排序算法(二)插入、希尔、快速排序

本文详细介绍了三种排序算法:插入排序、希尔排序和快速排序。插入排序通过逐步比较并插入元素来建立有序序列,希尔排序则改进了插入排序,通过增量分组减少比较次数。快速排序采用分治策略,选取枢纽元并进行数组分割,实现高效排序。文中还讨论了选取枢纽元和分割数组的关键点。

插入排序

思路: 插入排序由N-1趟排序组成,N是数组长度。将数组分成有序和无序两个部分,将无序部分的第一个元素作为待插入元素与有序部分的元素从后往前依次比较,在有序部分找到比它小的元素,此时比它小的元素后一个位置就是待插入元素插入的位置,后面的元素后移位置即可。

第一轮排序:将数组第1个元素看成有序状态,2-N个元素是无序状态,将第2个元素(待插入元素)与第1个元素比较(第1个元素是待查入元素前一个索引位置的元素

  • 如果第2个元素小于第1个元素时,将第1个元素后移一个位置,插入索引往前移动一位。
  • 否则,认为第2个元素是有序状态中最大的一个元素,将第2个元素放在当前插入索引的后一个位置上,并结束本轮的插入排序。
    此时将得到前两个元素是有序状态,3-N个元素是无序状态的数组。继续进行相同的比较插入操作。

第二轮排序 : 数组1,2两个元素是有序部分,3–N个元素无序。将第3个元素作为待插入元素先与2元素比较,再与1元素比较找到合适位置插入,结果形成一个前三个元素有序,4-N个元素无序的状态。直到第N-1轮排序后得到最终结果。

import java.util.Arrays;

public class InsertSort {
    public static void main(String[] args) {
        int[] arr = {34,8,64,51,32,21};
        insertSort(arr);
    }
    private static void insertSort(int[] arr){
        for(int i=1;i<arr.length;i++){
            int temp = arr[i];  //第i轮要插入的数
            int index = i-1;    //从插入的数的前一个索引位置开始比较
            //当索引不越界,且要插入的值比索引值小则将索引值后移一位,索引再前移1位
            while (index>=0 && temp <arr[index]){  
                arr[index+1]=arr[index];
                index--;
            }
            arr[index+1]=temp;
            System.out.println("第"+i+"次排序");
            System.out.println(Arrays.toString(arr));
        }
    }
}
排序结果:
第1次排序
[8, 34, 64, 51, 32, 21]2次排序
[8, 34, 64, 51, 32, 21]3次排序
[8, 34, 51, 64, 32, 21]4次排序
[8, 32, 34, 51, 64, 21]5次排序
[8, 21, 32, 34, 51, 64]

直接插入排序在对于一个{2,3,4,5,1}这类的数组排序时,最后1进行插入比较时,将与前面所有的元素比较,会很大影响排序效率。基于此,希尔排序发明了出来。

希尔排序

希尔排序也可以归类为一种插入排序,它通过比较相距一定间隔的元素来工作;各趟比较所用的距离随着算法进行而减小,直到最后一趟排序只比较相邻元素。因此希尔排序也叫缩减增量排序
其与插入排序的不同之处在于,其通过控制增量将原数组进行里分组,在对分组后的数据进行了插入排序,这样可以更快的将小的数往前移动。具体代码如下

import java.util.Arrays;

public class ShellSort {
    public static void main(String[] args) {
        int[] arr= {81,94,11,96,12,35,17,95,28,58,41,75,15};
        shellSort(arr);
    }
    private static void shellSort(int[] arr){
        for(int gap=arr.length/2;gap>0;gap/=2){ //gap为希尔增量,逐渐缩减到1
            //内部为插入排序,只是插入时比较的是相隔一个增量的数据
            for(int i =gap;i<arr.length;i++){
                int temp = arr[i];
                int index = i-gap;
                while (index>=0 && temp<arr[index]){
                    arr[index+gap] = arr[index];
                    index-=gap;
                }
                arr[index+gap]=temp;
            }
            System.out.println(gap+"排序后");
            System.out.println(Arrays.toString(arr));
        }
    }
}
结果:
6增量排序后
[15, 94, 11, 58, 12, 35, 17, 95, 28, 96, 41, 75, 81]
3增量排序后
[15, 12, 11, 17, 41, 28, 58, 94, 35, 81, 95, 75, 96]
1增量排序后
[11, 12, 15, 17, 28, 35, 41, 58, 75, 81, 94, 95, 96]

希尔增量可能不是很好,因为希尔增量对未必互素,因此较小的增量可能影响较小。Hibbard增量,形如:1,3,7… 2 k − 1 2^{k}-1 2k1 在实际运用中效果更好。

快速排序

快速排序是一种分治的递归算法。经典的快速排序算法由以下步骤组成:

  1. 待排序数组arr[],如果arr[]元素个数为0或者1,直接返回该数组;
  2. 取arr[]中任意一个元素v称之为枢纽元
  3. 将arr[]中的其余元素与枢纽元v比较,比v小的放在v左边形成小数集合arr1,比v大的放在v右边形成大数集合arr2;
  4. 再将arr1递归上述步骤,后跟v,再将arr2递归上述步骤。

影响快速排序性能的关键主要在与上述第2步选取枢纽元第3步分割数组上。

选取枢纽元: 选取枢纽元一种安全的作法是随机的选取,但是随机数的生成开销很大。因此常用三数中值的分割方法。做法是
采用左端、右端和中心位置的上三个元素的中值作为枢纽元

分割策略: 将枢纽元与数组最后一个元素交换,使其离开要被分割的数据段。设置两个索引指针,i从第一个元素开始,j从倒数第二个元素开始。当i在j的左边时,我们将i右移,移过那些小于枢纽元的元素,并将j左移,移过那些大于枢纽元的元素。当i和j停止时,i指向一个大于枢纽元的数,j指向一个小于枢纽元的数。如果i在j的左边,那么将这两个元素互换,这样就把小元素推向了左边,大元素推向了右边。重复该步骤,直到i和j交错为止。最后将枢纽元与i所指的元素交换。(如果遇到i和j遇到了与枢纽元相等的元素,考虑让i和j都停止移动,这样可以使分割的数组更均衡,虽然相同的元素进行了交换没有意义)

未采取三数中值选取枢纽元
import java.util.Arrays;
public class QuickSort {
    public static void main(String[] args) {
        int[] arr = {8,1,4,9,0,3,5,2,7,6};
        quickSort(arr,0,arr.length-1);
        System.out.println(Arrays.toString(arr));
    }
    private static void quickSort(int[] arr,int left,int right){
        int pivot = (left+right)/2;
        int i =left;
        int j =right;
        int temp=0;
        while (i<j){
            while (arr[i]<pivot){
                i++;
            }
            while (arr[j]>pivot){
                j--;
            }
            if(i<j){
                 temp =arr[i];
                 arr[i]=arr[j];
                 arr[j]= temp;
            }else
                break;
  //此时左侧值等于枢纽值,说明在交换前右侧是在等于枢纽值处停下,
  //需要让右侧索引前移一位否则死循环
            if(arr[i]==pivot){ 
                j--;
            }
            //同上
            if(arr[j]==pivot){
                i++;
            }
        }
        if(i==j){
            i++;
            j--;
        }
        if(left<j){
            quickSort(arr,left,j);
        }
        if(right>i){
            quickSort(arr,i,right);
        }
    }
    结果:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
对枢纽元的选取和分割策略加以了改进
import java.util.Arrays;
public class QuickSort {
    public static void main(String[] args) {
        int[] arr = {8,1,4,9,0,6,5,8,6,7};
        quickSort(arr,0,arr.length-1);
        System.out.println(Arrays.toString(arr));
    }
    private static void quickSort(int[] arr,int left,int right){
        int pivot = median3(arr,left,right);
        int i =left;
        int j =right;
        int temp=0;
        while (i<j) {
            while (arr[++i]<pivot){
            }
            while (arr[--j]>pivot){
            }
            if(i<j){
                temp =arr[i];
                arr[i]=arr[j];
                arr[j]=temp;
            }else
                break;
        }
        if(left<j){
        quickSort(arr,left,j);
        }
        if(right>i) {
            quickSort(arr, i, right);
        }
    }
    //采用三数中值选取枢纽元。
    private static int median3(int[] arr,int left,int right){
        int center = (left+right)/2;
        int temp = 0;
        //将左数,中间数,右数做比较,最小的放在左边,众位数放中间,最大的放右边
        //返回中间数就是三数的中位数
        if(arr[center]<arr[left]){
            temp = arr[center];
            arr[center]=arr[left];
            arr[left]=temp;
        }
        if(arr[right]<arr[left]){
            temp = arr[right];
            arr[right]=arr[left];
            arr[left]=temp;
        }
        if(arr[right]<arr[center]){
            temp = arr[right];
            arr[right]=arr[center];
            arr[center]=temp;
        }
        return arr[right];
    }
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值