排序算法(Java)

控制台运行编译java程序带中文乱码问题解决办法: javac -encoding utf-8 Test.java

稳定性:排序算法需要保留数组中重复元素的相对位置。(具体详见算法第四版P217)

冒泡排序

思想: 两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止。

时间复杂度: O(N2)

空间复杂度: O(1)

稳定性: 稳定

public class Sort {
    public static void sort(int[] nums) {
        int N = nums.length;
        int temp = 0;
        for (int i = 0; i < N-1; i++) {
            for (int j = 0; j < N-i-1; j++) {
                if (nums[j] > nums[j + 1]) {
                    temp = nums[j];
                    nums[j] = nums[j+1];
                    nums[j+1] = temp;
                }
            }
        }
    }
}

选择排序

思想: 每一趟在n-i+1(i=1,2,…,n-1)个记录中选取关键字最小的记录作为有序序列的第i个记录。

时间复杂度: O(N2)

空间复杂度: O(1)

稳定性: 不稳定(说明:举个例子,序列5 8 5 2 9, 我们知道第一遍选择第1个元素5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了,所以选择排序不是一个稳定的排序算法)

public class Sort {
    public static void sort(int[] nums) {
        int N = nums.length;
        for (int i = 0; i < N; i++ ) {
            int min = i;
            for (int j = i+1; j < N; j++) {
                if (nums[j] < nums[min]) {
                    min = j;
                }
            }
            if (min != i) {
                temp = nums[i];
                nums[i] = nums[min];
                nums[min] = temp;   
            }       
        }
    }
}

插入排序

思想: 将一个记录插入到已经排序好的有序表中,从而得到一个新的、记录数增1的有序表

时间复杂度: O(N2)

空间复杂度: O(1)

稳定性: 稳定

public class Sort {
    public static void sort(int[] nums) {
        int N = nums.length;
        int temp = 0;
        for (int i = 1; i < N; i++ ) {
            for (int j = i; j > 0 && nums[j] < nums[j-1]; j--) {
                temp = nums[j];
                nums[j] = nums[j+1];
                nums[j+1] = temp;
            }
        }
    }
}

改进版本

public class Sort {
    public static void sort(int[] nums) {
        int N = nums.length;
        int i = 0; 
        int j = 0;
        for (i = 1; i < N; i++) {
            if (nums[i] < nums[i-1]) {
                int temp = nums[i];
                for (j = i-1; (j >= 0) && (nums[j] > temp); j--) {
                    nums[j+1] = nums[j];
                }
                nums[j+1] = temp;
            }
        }
    }
}

希尔排序(插入排序升级)

思想: 将数据分为若干组记录,然后分别对每一组做插入排序。

时间复杂度: O(NlgN)

空间复杂度: O(1)

稳定性: 不稳定

public class Sort {
    public static void sort(int[] nums) {
        int N = nums.length;
        int temp = 0;
        int h = 1;
        int i = 0; 
        int j = 0;
        //固定步长:1,4,13,40...
        while (h < N/3) {
            h = 3*h + 1;
        }
        while (h >= 1) {
            //下面操作和插入排序算法基本相同
            for (i = h; i < N; i++) {
                if (nums[i] < nums[i - h]) {
                    int temp = nums[i];
                    for (j = i-h; (j >= 0) && (nums[j] > temp); j = j-h) {
                        nums[j+h] = nums[j];
                    }
                    nums[j+h] = temp;
                }
            }
            h = h / 3;
        }
    }
}

快速排序(冒泡排序增强)

思想: 选取一个轴值(比较的基准),将待排序记录分为独立的两个部分,左侧记录都是小于或等于轴值,右侧记录都是大于或等于轴值,然后分别对左侧部分和右侧部分重复前面的过程,也就是左侧部分又选择一个轴值,又分为两个独立的部分,这就使用了递归了。到最后,整个序列就变得有序了。

时间复杂度: O(NlgN)

空间复杂度: O(lgN)~O(N)

稳定性: 不稳定

public class Sort {
    public static void sort(int[] nums) {
        //消除对输入的依赖
        //StdRandom.shuffle(a);   
        sort(nums, 0, nums.length - 1);
    }

    public static void sort (int[] nums, int lo, int hi) {
        if (hi <= lo) return;
        //切分
        int j = partition(nums, lo, hi);
        sort(nums, lo, j-1);
        sort(nums, j+1, hi);
    }

    public static int partition(int[] nums, int lo, int hi) {
        //定义左右扫描的指针
        int i = lo, j = hi + 1;
        //定义切分的元素d
        int point = nums[lo];
        int temp = 0;
        while (true) {
            while (nums[++i] < point) if (i == hi) break;
            while (point < nums[--j]) if (j == lo) break;
            if (i >= j) break;

            //交换两个元素
            temp = nums[i];
            nums[i] = nums[j];
            nums[j] = temp;
        }
        //将切分元素放到数组中合适位置
        temp = nums[lo];
        nums[lo] = nums[j];
        nums[j] = temp;

        return j;
    }
}

堆排序(选择排序增强)

思想: 利用优先队列的删除最大元素的特点,依次将将删除的最大元素保存起来,就有序了。当然,这里的优先队列是用堆实现的。

第一步:需要保证堆有序,也就是每一个父节点要比它的任意子节点要大;

第二步:使用堆的下沉操作来实现排序;

时间复杂度: O(NlgN)

空间复杂度: O(1)

稳定性: 不稳定

public class Sort {
    public static void heapSort(int[] a) {
        int N = a.length;
        //第一步:通过下沉操作来实现堆有序,这里只需要操作数组中前面的一半元素即可
        for (int k = N / 2; k >= 1; k--) {
            sink(a, k, N);
        }
        //第二步:将堆有序的数组使用下沉操作来得到有序数组
        while (N > 1) {
            exch(a, 1, N--);
            sink(a, 1, N);
        }
    }

    //堆下沉操作
    public static void sink(int[] a, int k, int N) {
        while (2*k <= N) {
            int j = 2*k;
            //取两个子节点中较大的一个
            if (j < N && less(a, j, j+1)) j++;
            //比较如果父节点比子节点中较大的一个小,则交换
            if (!less(a, k, j)) break;
            exch(a, k, j);
            //继续往下面遍历
            k = j;
        }
    }
    //之所以取i-1,是因为堆中下标是从1开始的,需要还原到数组中从0开始的。
    public static boolean less(int[] a, int i, int j) {
        return a[i-1] < a[j-1];
    }

    public static void exch(int[] a, int i, int j) {
        int temp = a[i-1];
        a[i-1] = a[j-1];
        a[j-1] = temp;
    }        
}

归并排序

思想: 要将一个数组排序,可以先(递归地)将它们分成两半分别排序,然后将它们的结果归并起来。

时间复杂度: O(NlgN)

空间复杂度: O(N)

稳定性: 稳定

public class Sort {
    //1.将两个有序数组合并为一个有序数组
    public static void merge(int[] a, int lo, int mid, int hi) {
        //将a[lo..mid] 和 a[mid+1..hi]归并
        //i代表左半边索引,j代表右半边索引
        int i = lo, j = mid + 1;
        //定义一个辅助数组
        int[] aux;
        //将a[lo..hi]复制到aux[lo..hi]
        for (int k = lo; k <= hi; k++) {
            aux[k] = a[k];
        }
        for (int k = lo; k <= hi; k++) {
            //左半边元素用尽,取右半边的元素
            if (i > mid) {
                a[k] = aux[j++];
            //右半边元素用尽,取左半边元素    
            } else if (j > hi) {
                a[k] = aux[i++];
            //右半边的当前元素小于左半边的当前元素,则取右半边的元素
            } else if (aux[j] < aux[i]) {
                a[k] = aux[j++];
            //左半边的当前元素小于等于右半边的当前元素,则取左半边的元素
            } else {
                a[k] = aux[i++];
            }
        }
    }
    //自顶向下
    pubic static void sort (int[] a, int lo, int hi) {
        if (hi <= lo) {
            return;
        }
        int mid = lo + (hi - lo) / 2;
        //将左半边排序
        sort(a, lo, mid);
        //将右半部分排序
        sort(a, mid+1, hi);
        //归并结果
        merger(a, lo, mid, hi);
    }

    //自底向上
    public static void sort (int[] a) {
        int N = a.length;
        int[] aux = new int[N];
        //定义子数组的大小
        for (int sz = 1; sz < N; sz = sz + sz) {
            //子数组的索引                        
            for (int lo = 0; lo < N-sz; lo += sz+sz) {  
                //lo+sz+sz-1可能越界,因此需要一个min函数来取边界                
                merge(a, lo, lo+sz-1, Math.min(lo+sz+sz-1, N-1));       
            }
        }
    }
}

总结

时间复杂度:
O(N)
计数排序、基数排序

O(N2)
冒泡排序、选择排序、插入排序

O(NlogN)
希尔排序、堆排序、快速排序、归并排序

空间复杂度:
O(1)
插入排序、选择排序、冒泡排序、堆排序、希尔排序

O(logN) ~ O(N)
快速排序

O(N)
归并排序

O(M) M为桶的数量
计数排序、基数排序

稳定性:

稳定的排序算法:
冒泡排序、插入排序、归并排序、计数排序、基数排序、桶排序

不稳定的排序算法:
选择排序、快速排序、希尔排序、堆排序

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章