经常使用排序算法简要分析

选择排序

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

时间复杂度:O(n²)数组

实现:markdown

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号(iui

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的希尔排序。
时间复杂度:未知
适用范围:数据规模较大,数据较离散。
实现:spa

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();
    }
}

快速排序

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

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;
    }
}
相关文章
相关标签/搜索