1.冒泡排序
**冒泡排序:是一种交换排序,它的基本思想是:两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止。冒泡的实现在细节上可以有很多种变化,我们将分别就3种不同的冒泡实现代码,来讲解冒泡排序的思想。**(1.)下面这个是最简单的冒泡排序,或者说它还不是冒泡,因为不满足相邻两两相比,它只是最简单的交换排序而已。
它的思路就是让每一个下标值,都和它后面的每一个下标值比较,如果大则交换,这样第一位置的下标值在一次循环后一定编程最小值,循环完后也就排好了。
public static void main(String[] args) {
int[] value = {6,8,1,9,7,12,16,94,23,4};
for (int i = 0; i < value.length; i++) {
for (int j = (i+1); j < value.length; j++) {
if(value[j] < value[i]){
int hou = value[j];
value[j] = value[i];
value[i] = hou;
}
}
System.out.println(value[i]);
}
它应该算是最容易写出的排序代码了,不过这个简单易懂的代码,却也有缺陷的,观察后发现,在排序好1和2的位置后,对其余下标值的排序没有什么帮助。也就是说,这个算法的效率非常低的。
时间复杂度算一下:O(n*2)
空间复杂度算一下:
2.上面的那个果然很麻烦,一个值来来回回要比较好多遍,我们来看看正宗的冒泡排序法。
int[] var = {9,1,5,8,3,7,4,6,2};
for(int i=1; i<=var.length; i++){
for (int j=var.length-1; j>=i; j-- ){ //注意这里是从后向前比较的 var.length-1 这里要注意长度为8,下标从0开始所以减一。
if(var[j] < var[j-1]){ //计算方式法生了变化
int amp = var[j];
var[j] = var[(j-1)];
var[(j-1)] = amp;
}
}
}
关键字序列是{9,1,5,8,3,7,4,6,2},当i=1时,变量j由8反向循环到1,逐个比较,将交小值交换到前面,直到最后找到最小值放置在了第1的位置。事实上,在不断循环的过程中,除了将关键字1放到第一的位置,我们还将关键字2从第九位置到了第三的位置,显然这一算法比前面的要有进步,在上十万条数据的排序过程中,这种差异会体现出来,较小的数字如同气泡般慢慢浮到上面,因此就将此算法命名为冒泡算法。
3.冒泡排序优化
这样的冒泡程序是否还可以优化呢?答案是肯定的。试想一下,如果我们待排序的序列是{2,1,3,4,5,6,7,8,9},也就是说,除了第一和第二的关键字需要交换,别的都已经是正常的顺序。当i=1时,交换了2和1,此时序列已经有序,但是算法仍然不依不饶地将i=2 到 9 以及每个循环中j循环都执行了一遍,尽管并没有交换数据,但是之后的大量比较还是大大地多余了。
boolean flag = true;
int[] val = {2,1,8,7,5,6,3,4,9};
for(int i=1; i<=val.length; i++){
if(flag == true){
flag = false;
for(int j = val.length-1; j >= i ; j--){
if(val[j] < val[j-1]){
int amp = val[j];
val[j] = val[(j-1)];
val[(j-1)] = amp;
flag = true;
}
System.out.print(j);
}
}
System.out.println();
}
代码改动的关键就是在i变量的 for循环中,增加了对flag是否为true的判断。经过这样的改进,冒泡排序在性能上就有了一些提升,可以避免因已经有序的情况下的无意义循环判断。
2.简单选择排序
**简单选择排序法(Simple Selection Sort)就是通过n-i次关键字间的比较,从n-i+1个记录中选出关键字最小的记录,并和第i(1<= i <= n)个记录交换之。**int var = {9,1,5,8,3,7,4,6,2};
for(int i = 0; i < var.length; i++){
int len = i;
for(int j = i+1; j <= var.length-1 ; j++){
if(var[len] > var[j]){
int value = var[len];
var[len] = var[j];
var[j] = value;
}
}
}
代码应该说不难理解,针对待排序的关键字序列是{9,1,5,8,3,7,4,6,2};对 i 从 0 开始循环到 7。
当i=0时,var[i]=9, len开始是0,然后是与j = 1 到 8比较var[len] 与 var[j]的大小,因为j=2时最小,所以 len=2.
最终交换了 var[1] 与 var[0]的值。
时间复杂度: O(n的2次方)
虽然和冒泡排序的时间复杂度是一样的 但是性能上稍微比冒泡好一点点。
3.直接插入排序
**直接插入排序(Straight Insertion Sort)的基本操作是将一个记录插入到已经排好序的有序列表中, 从而得到一个新的 、记录数增1的有序列表。**// 直接插入排序算法
int[] var = {0,9,6,4,2,8,1,3,7,5};
for(int i = 2; i<=var.length-1; i++){
if(var[i] < var[i-1]){
var[0] = var[i];
int j = 0;
for(j = i-1; var[j]>var[0]; j--)
var[j+1]=var[j];
var[j+1] = var[0];
var[0] =0;//恢复默认值
}
}
for(int i : var ){
System.out.print(i);
}
这个排序有点恶心。var数组的第一个值是0,其实它是空的。它只是用来占个空位置的,插入时用来暂存数据的。为啥要这样写呢,原因的在第二个循环条件里被限制了。或者说i最小是2,查过2时会有一个下标越界报错信息,就算不报错那排除出来的顺序也是不正确的,那var[0]的值也无法被正常排序。就是因为下标是0。目前还没想出来怎么办。
4.希尔排序
如何让待排序的记录个数较少呢?佷容易想到的就是将原本有大量记录数的记录进行分组,分割成若干个子序列,此时每个子序列待排序的记录个数就比较少了,然后在这些序列内分别进行直接插入排序,当整个序列都基本有序时,注意只是基本有序时,再对全体进行一次直接插入排序。**所谓的基本有序,就是小的关键字基本在前面,大的基本在后面,不打不小的基本在中间,像{2,1,3,6,4,7,5,8,9}这样可以成为基本有序了,但像{1,5,9,3,7,8,2,4,6}这样的9在第三位,2在倒数嗲三位就谈不上基本有序了。** 其实问题就在这里,我们分割待排序记录的目的是减少待排序记录的个数,并使整个序列向基本有序发展。而如上面这样分完组后就各自排序的方法达不到我们的要求。因此,我们需要采取跳跃分割的策略:**将其距某个“增量”的记录组成一个子序列,这样才能保证在子序列内分别进行直接插入排序后得到的结果是基本有序而不是局部有序。** int[] var = {0,9,1,5,8,3,7,4,6,2};
int i , j;
int increment = var.length-1;
do {
increment = increment/3+1; //增量排序
for( i=increment+1; i<=var.length-1; i++){
if(var[i] < var[i-increment]){ // 需将var[i] 插入到有序增量子表
var[0] = var[i]; // 暂存在 var[0]
for(j=i-increment; j>0&&var[0] < var[j]; j-=increment){
var[j+increment] = var[j] ; //记录后移, 查找插入位置
}
var[j+increment] = var[0];
}
}
for (int v : var) //可以看看每次的变化
System.out.print(v);
System.out.println();
} while (increment > 1);
for(int s : var) //输出最后的结果
System.out.print(s);
}
希尔排序的精华所在就是它将关键字比较小的记录,不是一步一步的往后挪动,而是跳跃式的向前移动,从而使得每次完成一轮循环后,整个序列就朝着有序坚实的迈进一步。
希尔排序的关键并不是随便分组后各自排序,而是将相隔某个“增量”的记录组成一个子序列,实现跳跃式的移动,是的排序的效率提高。
这里“增量”的选取就是非常关键了,increment = increment/3+1 的方式选取增量的,可究竟应该选取什么样的增量才最好呢。现在还没有明确的解决这个问题。需要注意的是**增量序列的最后一个增量值必须等于1才行。**另外由于记录是跳跃式的移动,希尔排序并不是一种稳定的排序算法。
5.堆排序
根据比较结果对其他记录做出相应的调整,那样排序的总体效率就会非常高了,而堆排序(Heap Sort),就是对简单选择排序进行的一种改进,这种改进的效果是非常明显的,堆排序算法是Floyd 和 Williams 在1964年共同发明的,同是,他们发明了“堆”这样的数据结构。 **堆是具有下列性质的完全二叉树:每个结点的值都大于或者等于其左右孩子结点的值,称为大顶堆; 或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。** **堆排序**就是利用堆(假设利用大顶堆)进行排序的方法。它的基本思想是,**将待排序的序列结构成一个大顶堆。此时,整个序列的最大值就是堆顶的根结点,将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次小值。如此反复执行,便能得到一个有序序列了。**
public static void main(String[] args) {
// 堆排序
//第一个for要完成的就是将现在的待排序序列构建成一个大顶堆
//第二个循环要完成的就是逐步将每个最大的值的根结点与末尾元素交换,并且在调整其成为大顶堆
int[] var = {0,50,10,90,30,70,40,80,60,20};
int i;
for (i = (var.length-1)/2; i>0; i--) { //把var构建成一个大顶堆
//如何由一个无序序列构建成一个堆。
var = HeapAdjust(var,i,var.length-1);
}
for(i=(var.length-1); i>1 ; i--){
//将堆顶记录和当前未经排序子序列的最后一个记录交换
swap(var,1,i);
// 将var重新调整为大顶堆
var = HeapAdjust(var, 1, i-1);
}
//输出结果
for(int v : var){
System.out.print(v+"-");
}
}
// 将堆顶的记录 与 未排序子序列的最后一个记录交换
public static void swap(int[] var,int s, int m){
int top = var[s];
var[s] = var[m];
var[m] = top;
}
// 最终要的方法 是这个
public static int[] HeapAdjust(int[] var, int s ,int m){
int temp, j;
temp = var[s];
//解释一下:这里j变量为什么是从2*s开始的?又为什么是j*=2递增?
//原因还是二叉树的性质,因为我们这棵是完全二叉树,当前序号是s,其左孩子的序号一定是2S,右孩子的序号一定是2s+1
//它们的孩子当然也是以2的位数序号增加,因此j变量才是这样循环。
for(j=2*s; j<=m; j*=2){ //沿关键字较大的孩子结点乡下筛选
if(j<m && var[j]<var[j+1]){
++j;
}
if(temp >= var[j]){
break;
}
var[s] = var[j];
s=j;
}
var[s] = temp;
return var;
}
时间复杂度为:O(nlogn)。由于堆排序对原始记录的排序状态并不敏感,因此它无论是最好还是最坏还是平均时间复杂度均为O(nlogn)。这在性能上好过上面的几种排序。
空间复杂度上,它至有一个用来交换的暂存单位,也非常不错了,不过由于记录的比较与交换是跳跃式进行,因此堆排序也是一种不稳定的排序方法。
另外,由于初始结构对所需要的比较次数较多,因此,它并不适合待排序序列个数较少的情况。