八大排序算法总结之一(冒泡排序,快速排序,直接插入排序,希尔排序)

排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序一般是排序的数据量很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。我们经常说的八大排序说的就是内部排序。 
这里写图片描述


冒泡排序算法(从后往前)bubbleSort

  • 比较相邻的两个数,若前面的数大于后面的数,则交换两个数;
  • 这样对0到n-1个数据进行遍历,那么最大的数据就会被排到n-1处;
  • 重复步骤,直至再也不能交换。
public static void bubbleSort1(int [] input,int n){
        //外围循环n-1次,每次确定一个元素的位置,位于尾部
        for(int i=0;i<n-1;i++){
            //内部循环,相邻元素进行比较,比较次数逐步减1
            for(int j=0;j<n-1-i;j++){
                //从小到大排序
                if(input[j]>input[j+1]){
                    int temp = input[j];
                    input[j] = input[j+1];
                    input[j+1] = temp;
                }
            }
        }

    }
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

对于一些数据,前面部分是乱序的,而后面部分是有序的。当前面的排序好后,后面的元素排序还需要重复前面的操作,这种做法是非常多余的。对与这种情况可以做出优化,比如加上一个标志(flag),初始值为false,有交换则置为true,如果某次排序没有交换元素,则表明后面的元素是有序的,跳出循环,排序结束。

public static void bubbleSort2(int [] input,int n){
        //外围循环n-1次,每次确定一个元素的位置,位于尾部
        for(int i=0;i<n-1;i++){
            //标记位,如果这一趟发生了交换,则为true,否则为false。明显如果有一趟没有发生交换,说明排序已经完成。
            boolean flag = false;
            //内部循环,相邻元素进行比较,比较次数逐步减1
            for(int j=0;j<n-1-i;j++){
                //从小到大排序
                if(input[j]>input[j+1]){
                    int temp = input[j];
                    input[j] = input[j+1];
                    input[j+1] = temp;
                    //发生交换,置flag为true
                    flag=true;
                }
            }
            //没有发生交换,则表明已是有序,跳出循环
            if(flag==false){
                break;
            }
        }       
    }   
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

时间复杂度O(n^2) , 算法稳定性:稳定(如果两个元素相等,我想你是不会再无聊地把他们俩交换一下)


快速排序算法(快排):quickSort

它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。 
注意:第一遍快速排序不会直接得到最终结果,只能确定一个数的最终位置。为了得到最后结果,必须继续分解数组,直到数组不能再分解为止(只有一个数据),才能得到正确结果。

  • 先从数列中取出一个数作为基准数
  • 分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
  • 再对左右区间重复第二步,直到各区间只有一个数。

快速排序算法是基于分治法的:

分治法+挖坑( 先从后向前找,再从前向后找): 
3 4 6 8 9 10 5 2 16 
首先选定一个枢纽数3,pivotkey=a[0] , a[0]被保存到pivotkey中,可以被认为,在a[0]上挖了一个坑,可以将其他数据填充到这里来。然后从后向前找到小于pivotkey的值2,a[0]=a[7],a[0]上的坑被a[7]填上,结果又形成了一个新坑a[7].

public static void quickSort(int low,int high,int []input){
        if(low<high){
            int pivotloc = partion1(low,high,input);
            quickSort(low,pivotloc-1,input);
            quickSort(pivotloc+1,high,input);
        }   
    }
    public static int partion1(int low,int high,int[]input){
        //开始挖坑,input[low]为枢纽,其值保存在pivotkey中,可以将其他数据填充过来
        int pivotkey = input[low];
        while(low<high){
            //从后向前,直到找到小于pivotkey的值
            while(low<high&&input[high]>=pivotkey){
                high--;
            }
            //加上判断条件是因为当high==low,不必操作下面的语句
            if(low<high){
                //在low的位置上填坑,又形成了一个新坑
                input[low] = input[high];
                //从下一个位置开始
                low++;
            }
            //从前向后,直到找到大于pivotkey的值
            while(low<high&&input[low]<=pivotkey){
                low++;
            }
            if(low<high){
                //在high的位置上填坑,又形成了一个新坑
                input[high]=input[low];
                //从下一个位置开始
                high--;
            }
        }
        //****low==high,退出时,将pivotkey填到这个坑****pivotkey一直只是用与比较没有被填充过,当low==high,将其填充
        input[low] = pivotkey;
        return low;
    }
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

分治法,但不挖坑,交换的方法。最后的那个坑不需要去填,因为在交换的过程中,已经处理好了。这样,没交换一次就需要进行3次赋值语句,而实际上,在排序过程中对枢纽节点的赋值是多余的。只需最后赋值,也就是上面的做法。下面是没有改进前的方法。

public static void quickSort(int low,int high,int []input){
        if(low<high){
            int pivotloc = partion2(low,high,input);
            quickSort(low,pivotloc-1,input);
            quickSort(pivotloc+1,high,input);
        }   
    }
public static void swap(int[] input,int begin,int end){
        int temp = input[begin];
        input[begin] = input[end];
        input[end] = temp;
    }

public static int partion2(int low,int high,int[]input){
        //枢纽节点
        int pivotkey = input[low];
        while(low<high){
            //从后向前,直到找到小于pivotkey的值
            while(low<high&&input[high]>=pivotkey){
                high--;
            }
            //加上判断条件是因为当high==low,不必操作下面的语句
            if(low<high){
                //交换
                swap(input,low,high);
                //从下一个位置开始
                low++;
            }
            //从后向前,直到找到大于pivotkey的值
            while(low<high&&input[low]<=pivotkey){
                low++;
            }
            if(low<high){
                //交换
                swap(input,low,high);
                //从下一个位置开始
                high--;
            }
        }
        //返回之前没有填坑,是因为在交换过程中已经完成
        return low;
    }
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

有时是以中间的数作为基准数的,要实现这个非常方便,直接将中间的数和第一个数进行交换就可以了。时间复杂度O(nlogn), 算法稳定性:不稳定。


直接插入排序: insertSort

直接插入排序(Insertion Sort)的基本思想是:每次将一个待排序的记录,按其关键字大小插入到前面已经排好序的子序列中的适当位置,直到全部记录插入完成为止。

直接插入排序时使用顺序查找,找到关键字应该插入的位置,然后将该位置后面的所有元素向后移动。然后将要插入的值插入目标位置。

public static void insertSort(int []input){
        for(int i=1;i<input.length;i++){
            //用来标记临界点
            int j=i-1;
            for(;j>=0;j--){
                //插入input[i]时,前面的都是有序的序列,直到找到一个小于input[i]的值input[j],那么该位置后面的一个位置就是input[i]的位置
                if(input[j]<input[i]){
                    break;
                }
            }
            //j==i-1,表明input[i]前面的一个数input[i-1]就小于input[i],就不必移动数组了
            if(j!=i-1){
                int temp = input[i];
                for(int k=i-1;k>j;k--){
                    input[k+1] = input[k];
                }
                //标记点的后一位就是input[i]的位置
                input[j+1]=temp;
            }       
        }
    }
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

改进(折半插入排序):在直接插入排序时,采用折半查找的方法找到插入的标记点,然后将标记点后面的元素从后向前依次移动一个位置。

public static void bInsertSort(int []input){
        for(int i=1;i<input.length;i++){
            int low=0;
            int high = i-1;
            //high作为标记节点,input[high]<input[i]
            while(high>=low){
                int middle = (low+high)/2;
                if(input[middle]<input[i])
                    low = middle+1;
                else
                    high=middle-1;
            }
            //保存input[i]
            int temp = input[i];
            for(int k=i-1;k>high;k--){
                input[k+1] = input[k];
            }
            input[high+1] = temp;           
        }
    }
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

折半插入排序所需附件的存储空间和直接插入排序相同,从时间复杂度上来讲,折半插入排序仅仅减少了关键字之间的比较次数,但移动次数不变。因此时间复杂度还是O(n^2). 算法稳定性:稳定。

改进:直接插入排序时,将收索和后移两个动作同时进行。

public static void insertSort3(int []input){
        for(int i=1;i<input.length;i++){
            //0~i-1位为有序,若第i位大于i-1位,忽略此次循环,相当于continue
            if(input[i-1]>input[i]){
                int temp = input[i];
                int j = i-1;
                //收索和移动同时进行
                for(;j>=0&&input[j]>temp;j--){
                    //input[j]后移一位,在input[i]处挖了一个坑
                    input[j+1] = input[j];
                }
                input[j+1] = temp;
            }
        }
    }
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

再对上面的方法进行改写, 用数据交换代替数据后移。

public static void insertSort2(int []input){
        for(int i=1;i<input.length;i++){
            for(int j=i-1;j>=0;j--){
                //基于有i前面的元素数有序的,交换之后input[i]的值向前移动了一位,直到有input[j]<input[j+1]为止
                if(input[j]>input[j+1]){
                    int temp = input[j];
                    input[j] = input[j+1];
                    input[j+1] = temp;
                }
            }
        }
    }
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

希尔排序::shellSort

希尔排序(Shell Sort)是插入排序的一种。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。 
基本思想是:先取一个小于n的整数d1作为第一个增量,把文件的全部记录分组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2(d2小于d1),重复上述的分组和排序;依次取d3、d4、…..直至取到dt=1为止,即所有记录放在同一组中进行直接插入排序。

这里写图片描述

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;

public class ShellSort {
    public static void shellInsert(int[]input,int dk){
        /*
         * 对数组做一趟希尔排序。本算法和直接插入排序相比,做了如下修改:
         * 前后记录位置的增量是dk,而不是1;
         * */
        //所有距离为dk的倍数的记录放在同一个组中,对组内元素进行直接插入排序
        for(int i=dk;i<input.length;i++){
            if(input[i-dk]>input[i]){
                //用于保存将要插入有序分组的元素input[i]
                int temp = input[i];
                收索和移动同时进行
                int j = i-dk;
                for(;j>=0&&input[j]>temp;j-=dk){
                    //input[j]后移dk位,在input[j]处挖了一个坑
                    input[j+dk] = input[j];
                }
                //填坑
                input[j+dk] = temp;
            }
        }       
    }
    public static void shellSort(int []input,int[]dk){
        for(int i=0;i<dk.length;i++){
            //执行多个分组插入排序,但最后一个dk,必须是1
            shellInsert(input,dk[i]);
        }
    }
    public static void main(String[] args) throws IOException {
        StreamTokenizer cin = new StreamTokenizer (new BufferedReader(new InputStreamReader(System.in)));
        while(cin.nextToken()!=cin.TT_EOF){
            int n =(int)cin.nval;
            int []input = new int[n];
            for(int i=0;i<n;i++){
                cin.nextToken();
                input[i] = (int)cin.nval;
            }
            int[]dk ={6,5,4,3,1};//{6,5,1}、{4,3,1}都是合理的
            shellSort(input,dk);
            for(int i=0;i<input.length;i++){
                System.out.print(input[i]+" ");
            }
        }
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

该方法实质上是一种分组插入方法。专家们提倡,几乎任何排序工作在开始时都可以用希尔排序,若在实际使用中证明它不够快,再改成快速排序这样更高级的排序算法. 本质上讲,希尔排序算法是直接插入排序算法的一种改进,减少了其复制的次数,速度要快很多。 原因是,当n值很大时数据项每一趟排序需要的个数很少,但数据项的距离很长。当n值减小时每一趟需要和动的数据增多,此时已经接近于它们排序后的最终位置。 正是这两种情况的结合才使希尔排序效率比插入排序高很多。希尔排序的时间复杂度与增量序列的选取有关,O(n^2)~O(nlog2n), 算法稳定性:不稳定。

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值