常用排序算法简要分析

选择排序

给定一个规模为n的数组,在n个数字中寻找最小的一个,然后与第0位置的数交换,然后在后n-1个数中找最小的,与第1个位置的数字交换。当交换了i次以后,数组左边的i个数字是有序的,右边n-i个数字无序。然后把在n-i个数字中寻找最小的,与i位置的数字交换位置。直到交换n次以后,整个数组有序。

时间复杂度:O(n²)

实现:

public class Selection{
    public static void sort(int[] a) {
        int t;
        for(int i=0;i<a.length;i++){
            int index=i;//用来保存剩余乱序数组中的最小值的位置
            for(int j=i+1;j<a.length;j++){
                if(a[index]>a[j]){//寻找最小值并保存位置
                    index=j;
                }
            }
            //找到最小值的位置后交换
            t=a[i];
            a[i]=a[index];
            a[index]=t;
        }
    }

}

插入排序

给定一个规模为n的数组,把第i号(i

public class Insertion{
    public static void sort(int[] a) {
        int t=0;
        for(int i=1;i<a.length;i++){
            int v=a[i],k;
            for(k=i-1;k>=0&&v<a[k];k--){//查找左边比a[i]的元素大的元素,右移。
                a[k+1]=a[k];
            }
            a[k+1]=v;//把a[i]放入比a[i]小的元素的后面
        }
    }
}

希尔排序

根据插入排序,如果n个元素中最小的数在最右端,那么该数字需要比较n-1次才可以到达目标位置,因为是逐个比较元素的大小的。如果把逐个比较改为相隔1个比较并移动、相隔1个比较并移动、相隔h个比较并移动,那么最右端的元素到目标位置的比较移动次数将会大幅度减小。所以,在每隔h个元素都比较移动完以后,n个数中将会出现n/h个有序对,他们彼此相隔h,然后把h–,然后再比较移动,直到h==1且比较移动完后,排序结束。插入排序就是h=1的希尔排序。
时间复杂度:未知
适用范围:数据规模较大,数据较离散。
实现:

public class Shell {
    public static void sort(int[] a){
        int h=1,t=0;
        h=a.length/2;
        while(h>=1){//直到相隔距离为1时,最后一次比较移动
            for(int i=h-1,j;i<a.length;i++){//从i位置开始,保证之前每隔h位置的元素都是有序的,一开始从h-1开始,左边只有h个元素,所以每一组都是有序的
                int base=a[i];//要插入的元素
                for(j=i-h;j>=0&&a[j]>base;j-=h){//把要插入的元素与该组的其他元素比较大小,如果被比较的元素较大,则右移h个位置
                        a[j+h]=a[j];
                }
                a[j+h]=base;//把要插入的元素插入到找到的位置
            }
            h/=2;//相隔距离每次减半
        }
//      display(a);
    }
}

归并排序

通过递归的方式,把n个元素等分成两部分,然后再将这两部分等分成四部分,以此类推,知道将n个元素分成n个单独的组,这n个组因为只有一个元素,所以是有序的。
然后把其中相邻的两个组合并,要求合并后的组也是有序的,于是形成了n/2个组。然后再相邻的组合并,形成n/4个组。依此类推,知道合并成只有一个组,排序完毕。
合并两个组时,可以将两个指针指向两个数组的第一个位置,然后比较大小,把小的放入临时数组,然后该指针后移。如果其中一个数组的指针到了最右,则把另一个数组剩下的元素复制到临时数组。
过程如下图:
这里写图片描述
时间复杂度:O(nlgn)
缺点:需要开辟一个与原数组相同大小的辅助数组
实现:

public class Merge {
    static int[] temp=null;
    public static void sort(int[] a){
        int start,end;
        temp=new int[a.length];
        start=0;
        end=a.length-1;
        merge(a,start,end);
//      display(a);
    }
    public static void merge(int[] a,int start,int end){
        if(start==end)
            return;
        int mid=(start+end)/2;

        merge(a,start,mid);//把数组分成两半,分别排序
        merge(a,mid+1,end);
        if(a[mid]<a[mid+1])//前一个数组中最大的数比后一个数组中最小的数小,说明两个数组合并后已经有序,所有不需要再排序
            return;
        mergeSort(a,start,end);
    }
    public static void mergeSort(int[] a,int start,int end){
        int mid=(start+end)/2;
        for(int i=start,j=mid+1,k=start;k<=end;k++){//i,j分别表示两个数组的第一个元素(物理上只有一个数组,但逻辑上分为两个数组)
            if(i>mid)   temp[k]=a[j++];//如果第一个数组比较结束,则以此把第二个数组所有元素复制到临时数组
            else if(j>end)  temp[k]=a[i++];//如果第二个数组比较结束,则以此把第一个数组所有元素复制到临时数组
            else if(a[i]<a[j])  temp[k]=a[i++];//如果第一个数组i位置元素比较小,则把a[i]复制到临时数组,并且i右移
            else    temp[k]=a[j++];//如果第二个数组j位置元素比较小,则把a[j]复制到临时数组,并且j右移
        }
        System.arraycopy(temp, start, a, start, end-start+1);//把临时数组中的元素复制到原数组
    }
    public static void display(int[] a){
        for(int i=0;i<a.length;i++)
            System.out.print(a[i]+" ");
        System.out.println();
    }
}

快速排序

1、 给定n个数,从start到end进行排序
2、 从左边往右边找到i满足a[i]>a[start]
3、从右边往左边找到j满足a[j]>a[start]
4、 交换a[i]和a[j]
5、 重复3、4,直到i>=j
6、 交换a[start]和a[j],此时满足a[0~j-1]都小于a[j], a[j+1,end]都大于于a[j]
7、 从1重新执行,此时start=start,end=j-1 和start=j+1,end=end,直到start>=end
过程图:
这里写图片描述
时间复杂度(平均):O(nlgn)
适用范围:重复数字少,有序数字少
实现:

public class Quick {
    public static void sort(int[] a) {
        sort(a, 0, a.length - 1);
    }

    private static void sort(int[] a, int start, int end) {
        if (start >= end)
            return;
        int i = partition(a, start, end);// 返回切割位置,此时i位置的元素大于[start,i-1],下雨[i+1,end]
        sort(a, start, i - 1);
        sort(a, i + 1, end);
    }

    private static int partition(int[] a, int start, int end) {// 要求返回位置的元素大于等于左边元素,返回位置的元素都小于等于右边的元素
        int i = start + 1, j = end;
        i = start;
        j = end + 1;
        int value=a[start];
        while (true) {
            while (a[++i] < value)
                // 向右寻找比a[start]大的数
                if (i == end)
                    break;
            while (a[--j] > value);// 向左寻找比a[start]小的数,但因为最左边是a[start],所以j>start恒成立

//              if (j == start)
//                  break;
            if (i >= j)// 如果i,j相遇或i>j,则结束寻找
                break;
            swap(a, i, j);
        }
        // j就是满足a[j]大于等于左边,小于等于右边的位置
        // 因为a[j]一定小于等于a[start]或者j等于start
        // a[i]一定大于等于a[start]或者i等于end
        // 根据前一种情况,i跟j不在同一位置的话,a[j]就是小于a[start]最右边的数,再往右的话就是a[i],而a[i]一定大于a[start]
        // 如果a[j]>a[start],就意味着j到了最左边(因为j是从右往左寻找比a[start]小的数的,如果没找到且停下来了,就意味着到头了)
        // 但是所谓的最左边就是a[start]所在位置,所以a[j]一定小于或等于a[start]
        swap(a, j, start);
        return j;

    }

    private static void swap(int[] a, int p, int q) {
        int t = a[p];
        a[p] = a[q];
        a[q] = t;
    }
}

堆排序

(二叉)堆有序定义:在一颗二叉树中,如果所有父节点不小于子节点,则该二叉树就是堆有序
下潜操作定义:如果一个父节点大于两个子节点或者子节点中的一个,那么把父节点与子节点中大的那个交换位置,然后把之前的父节点(现在已被交换到子节点的位置)与其新的子节点进行比较并交换,知道该节点大于其子节点,则结束操作。该操作称为下潜操作。

对于一个有序堆,序号为0的节点一定是最大的元素,把这个元素去掉,并把最后一个元素放到0位置,该堆又变成了无序堆。然后对0节点进行下潜操作,该堆重新变成了有序堆。然后继续移除、继续排序,最终该堆大小为0,原数组的位置变为一个有序序列。
这里写图片描述
该图为把一个有序堆的最大元素移除、排序最终得到有序数列的过程。
那么接下来的问题就是如何把一个无序堆构建成有序堆。
只要把一个堆自下而上进行下潜操作,那么最终就是一个有序堆。而最后一层节点没有子节点,所以不需要进行下潜操作。从倒数第二层的最后一个节点开始进行下潜操作,知道第一个元素,那么就可以形成一个有序堆,过程如下图:
这里写图片描述

算法复杂度:O(nlgn)

实现:

public class Heap {
    // 如下一颗二叉树数字表示其序号
    //
    //         0
    //     1       2
    //   3   4   5   6
    //
    // 根据二叉树的定义,序号为n的节点的父节点(如果存在)序号为n/2⌉-1,其子节点(如果存在)序号为n*2+1和n*2+2
    // 如果共有n个元素,则倒数第二次的最后一个节点序号为(n+1)/2⌉+1

    public static void sort(int[] a) {
        int n = a.length;
        for (int k = (int) (Math.ceil((double) (n + 1) / 2) + 1); k >= 0; k--)
            // 从倒数第二层的最后一个几点开始建堆
            sink(a, k, n);
        while (n > 1) {
            swap(a, 0, --n);
            sink(a, 0, n);
        }
    }

    public static void sink(int[] a, int k, int n) {// 下潜操作
        while (k * 2 + 2 < n) {
            int j = k * 2 + 1;// j表示第一个子节点
            if (a[j] < a[j + 1])// 如果第一个子节点小于第二个子节点
                j++;// 此时j表示第二个子节点
            if (a[k] > a[j])// 如果大的子节点小于父节点,则表示父节点大于两个子节点,则不需要继续下潜了
                break;
            swap(a, k, j);// 否则把父节点和子节点交换
            k = j;
        }
    }

    private static void swap(int[] a, int p, int q) {
        int t = a[p];
        a[p] = a[q];
        a[q] = t;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值