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

来源:互联网 发布:淘宝推广视频教学 编辑:程序博客网 时间:2024/05/17 06:00

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


冒泡排序算法:(从后往前)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), 算法稳定性:不稳定。

阅读全文
0 0
原创粉丝点击