一、冒泡排序
如图所示,如果有五个元素需要排序,那么需要排n-1趟,那么我们是不是可以写出一个循环出来,
for(int i = 1; i <= size-1; i++)
但是我们发现每一趟的比较次数随着趟数的增大而减小,所以我们是不是可以
修改一下循环呢
for(int i = size-1; i > 0; i--)
这样不就成了随着趟数的增大i在减小吗,现在我们算是写出了算法的框框了,我们还要比较元素,有图上分析可知,比较次数在逐趟减小,并且都是从下标为0开始比较的,于是,还需要一个循环来比较元素
for(int j =0; j < ?; j++)
此时,问题来了,临界值是什么,“?”是几呢,再分析一下,第一趟比较下标从0到4,第二趟比较下标从0到3发现小标的变化和i是一样的,并且可以等于i,于是修改循环
for(int j =0; j <= i; j++)
此时感觉很对了,但是仔细想想,这个for循环的内容是什么呢,应该是source[j] 和source[j+1]进行比较,然后交换,如果j可以等于i,那么j+1不就比i还大么,所以我们应该把等号去掉,变为
for(int j =0; j < i; j++)
为了使取名有意义,可以修改一下这两个循环
for(int outer = size - 1; outer > 0; outer--)
{
for(int inner = 0; inner < outer; inner++)
{
//比较逻辑
int temp = source[inner + 1];
source[inner + 1] = array[inner];
source[inner] = temp;
}
}
完整程序如下所示:
package com.darren.test.sort;
public class BubbleSortTest {
public static void main(String[] args) {
int[] array = new int[] { 3, 5, 9, 8, 6, 4, 3, 1, 8, 7 };
System.out.print("before sort:");
print(array);
System.out.println();
int size = array.length;
for (int outer = size - 1; outer >= 0; outer--) {
for (int inner = 0; inner < outer; inner++) {
if (array[inner] > array[inner + 1]) {
int temp = array[inner + 1];
array[inner + 1] = array[inner];
array[inner] = temp;
}
}
}
System.out.print("after sort:");
print(array);
}
private static void print(int[] source) {
for (Integer data : source) {
System.out.print(data);
System.out.print(", ");
}
}
}
二、插入排序
如图所示,如果有五个元素需要排序,那么需要排n-1趟,并且下标是从1到n-1,那么我们是不是可以写出一个循环出来
for(int i = 1; i < size; i++)
由图可知,我们每次都是把source[i]插入到左边,那么是不是应该把source[i]临时保存一下呢,我想是应该的
int temp = source[i];
那么算法内部是如何进行比较插入的呢,接下来进行分析,我们发现一直向左边插入,其实左边的序列一直是有序的,那么我们就会从右向左比较插入就行了,所以第二个for循环是倒着的
for(int j = ?; j >=0; j--)
从图上可知j可以为0,那么最大可以是多少呢,再分析一下,最大比i小1,所以修改for循环
for(int j = i - 1; j >=0; j--)
怎么比较呢
source[j] 和 source[j+1]进行比较。此时source[j+1]刚好等于source[i],因为j + 1 = i;
把变量取得有意义一点,结果为
for(int outer = 1; outer < size; outer++)
{
int sentinel = source[i];
for(int inner = outer - 1; inner >= 0; inner--)
{
if(source[inner] > source[inner + 1])
{
source[inner + 1] = source[inner];
source[inner] = sentinel;
}
}
}
完整程序如下所示:
package com.darren.test.sort;
public class InsertSortTest {
public static void main(String[] args) {
int[] array = new int[] { 3, 5, 9, 8, 6, 4, 3, 1, 8, 7 };
System.out.print("before sort:");
print(array);
System.out.println();
int size = array.length;
for (int i = 1; i < size; i++) {
int sentinel = array[i];
for (int j = i - 1; sentinel < array[j]; j--) {
array[j + 1] = array[j];
array[j] = sentinel;
if (j == 0) {
break;
}
}
}
System.out.print("after sort:");
print(array);
}
private static void print(int[] source) {
for (Integer data : source) {
System.out.print(data);
System.out.print(", ");
}
}
}
三、选择排序
如图所示,如果有五个元素需要排序,宏观上看需要排n-1趟,并且下标是从0到n-2(注意黑色下划线的起始位置),那么我们是不是可以写出一个循环出来
for(int i = 0; i < size - 1; i++)
第一个for循环并不关心具体是怎么选出一个最小的放左边的,只是先找到下标的变化规律,接着我们看内部是如何实现选出最小值放左边的,这个实现相对插入排序有所不同,只用圈圈不易表达,所以我们用一个具体例子来看。
对一个五个元素的数组进行排序:3 1 5 2 4
要比较,必须有一个参照物,我们假设每一趟中最左边元素(黑色下划线对应的最左边元素)为参照物(int minValueIndex = i),然后拿之后的一个元素(int j = i + 1)和它比较,如果比它小,那么就改变参照物,把新的最小的元素作为参照物,继续拿它之后的元素与之比较,直到本趟结束,因为要拿j和左边的元素比较而没有j + 1,所以j可取到n-1
即
for(int j = i + 1; j < size; j++)
这里有两个问题
1.选个元素作为参照物还是元素的下标?
2.每一趟比较完应该做些什么?
我们先回答第二个问题,每一趟比较完我们应该把最小的元素放到左边的位置上,需要进行一次元素交换
然后回答第一个问题,我们知道要进行元素交换,如果使用元素作为参照物,那么我只知道最小值是几,却不能和其他元素交换,因为我不知道最小值的下标,但是如果我们知道了最小值的下标,就能知道最小值,所以我们选用下标作为参照物
for(int outer = 0; outer < size - 1; outer++)
{
// 设置参照物为最左边元素
int min = outer;
for(int inner = outer + 1; inner < size; inner++)
{
if(source[inner] < source[min])
{
// 更新参照物
min = inner;
}
}
// 交换最小值和最左边值
int temp = source[outer];
source[outer] = source[min];
source[min] = temp;
}
package com.darren.test.sort;
public class SelectSortTest {
public static void main(String[] args) {
int[] array = new int[] { 3, 5, 9, 8, 6, 4, 3, 1, 8, 7 };
System.out.print("before sort:");
print(array);
System.out.println();
int size = array.length;
for (int outer = 0; outer < size - 1; outer++) {
int min = outer;
for (int inner = outer + 1; inner < size; inner++) {
if (array[inner] < array[min]) {
min = inner;
}
}
int temp = array[outer];
array[outer] = array[min];
array[min] = temp;
}
System.out.print("after sort:");
print(array);
}
private static void print(int[] source) {
for (Integer data : source) {
System.out.print(data);
System.out.print(", ");
}
}
}
总结:
几种简单排序算法的比较
一般情况下不使用冒泡排序,它过于简单了,以至于可以毫不费力的写出来,当数据很小的时候会有些使用价值。
选择排序虽然把交换次数降到最低,但比较次数仍然很大,当数据量很小,并且交换数据相对于比较数据更耗时的情况下可是使用选择排序。
大多数情况系,假设数据量比较小或基本有序时,插入排序算法是三种简单算法最好的选择,对于更大数据量来说,快速排序更好,以后会讲快速排序算法。
除了在速度方面比较算法外,还有一种对算法的衡量标准是算法需要的内存空间有多大。这三种简单算法除了初始的数组外几乎不需要其他内存空间。所有排序算法都
需要一个额外的变量来存储交换时的数据项。