冒泡排序、选择排序、插入排序、表插入排序

排序应用

应用广泛:一旦建立数据库后,就可能根据某些需求对它进行排序。比如,对员工按工资排序,对学生按年级排序,对商品按价格排序,对城市按人口增长率排序,对国家按GDP排序,以及对恒星按大小排序。

重要性:由于排序非常重要而且可能非常耗时,所以它已经成为计算机科学中一个被广泛研究的课题。

排序的稳定性:如果具有相同关键字的数据项,经过排序它们的顺序保持不变,这样的排序就是稳定的。


冒泡排序

概述:冒泡排序算法最简单,但效率最低。

算法:1和2比较,2和3比较,3和4比较...,把最大的逐步冒泡到最顶端。

效率
比较次数:
假如一共有5个人比较高低,一共要比较4轮,第一轮要比较4次,第二轮比较3次,第三轮比较2次,第四轮比较1次,总共需要比较4+3+2+1次。
这是一个有序数列,公式为(N-1+1)*(N-1)/2,即N*(N-1)/2,忽略1,N2/2
交换次数:
如果数字是随机的,大概有一半数据需要交换,即N2/4
交换和比较次数都和N2成正比,所以用大O表示法,冒泡排序表示为O(N2)

无论何时,只要看到双层循环,就可以怀疑这个算法的运行时间是O(N2)。外层循环N次,内部循环每次执行N次或几分之N次,这就意味着将大约需要N2次基本操作。

        int arrLength = arr.length;
        int temp = 0;
        // 假设一共有5个人比较个子高低
        // i的取值为最后一个需要比较大小的元素
        // 第一个轮回最后一次比较的两个数的第二个数的下标是4
        // 第二个轮回最后一次比较的两个数的第二个数的下标是3
        // 第三个轮回最后一次比较的两个数的第二个数的下标是2
        // 第四个轮回最后一次比较的两个数的第二个数的下标是1
        for (int i = arrLength - 1; i > 0; i--) {
            // 每一个轮回总是1和2比较,2和3比较,3和4比较...,所以j=0
            // 因为后面是j和j+1比较,所以j的最大取值范围是这一轮回最后一次比较时两个数的第一个数,也就是j < i
            for (int j = 0; j < i; j++) {
                if (arr[j] > arr[j + 1]) {
                    temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
        }
package MyTest;

import java.util.Arrays;
import java.util.Random;

public class OrderArray {
    public static void main(String[] args) {
        int[] arr = new int[]{100, 99, 56, 78, 50};
        //fill(arr);
        print(arr);
        println();
        sort(arr);
        print(arr);
    }

    private static void println() {
        System.out.println();
    }

    static void sort(int[] arr) {
        int arrLength = arr.length;
        int temp = 0;
        // 假设一共有5个人比较个子高低
        // i的取值为最后一个需要比较大小的元素
        // 第一个轮回最后一次比较的两个数的第二个数的下标是4
        // 第二个轮回最后一次比较的两个数的第二个数的下标是3
        // 第三个轮回最后一次比较的两个数的第二个数的下标是2
        // 第四个轮回最后一次比较的两个数的第二个数的下标是1
        for (int i = arrLength - 1; i > 0; i--) {
            // 每一个轮回总是1和2比较,2和3比较,3和4比较...,所以j=0
            // 因为后面是j和j+1比较,所以j的最大取值范围是这一轮回最后一次比较时两个数的第一个数,也就是j < i
            for (int j = 0; j < i; j++) {
                if (arr[j] > arr[j + 1]) {
                    temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
        }
    }

    static void fill(int[] arr) {
        Random random = new Random();
        for (int i = 0; i < 100; i++) {
            int num = random.nextInt(100);
            arr[i] = num;
        }
    }

    static void print(int[] arr) {
        for (int i = 0; i < 5; i++) {
            System.out.print(arr[i] + " | ");
        }
    }
}
View Code

选择排序

效率:选择排序改进了冒泡排序,将交换次数从O(N2)减少到O(N),但比较次数仍为O(N2)。

算法:从内部循环的一个轮回中选择出最小值并记录其下标值,当内部循环结束后,把这个最小值和最左边的数交换位置。

        int arrLength = arr.length;
        int temp = 0;
        int min = 0;
        // 假如有5个人需要按照个子高低排序
        // 第一轮假设下标0的元素是最小的
        // 第二轮假设下标1的元素是最小的
        // 第三轮假设下标2的元素是最小的
        // 第四轮假设下标3的元素是最小的,这是最后一轮比较,这次比较只有两个数,下标为3的数是这个数组倒数第2个数,所以i < arrLength-1
        for (int i = 0; i < arrLength - 1; i++) {
            min = i;
            // 把最小的数和第2个数、第3个数、第4个数依次比较...
            for (int j = i + 1; j < arrLength; j++) {
                // 如果还有一个数a比这个数小,就把数a的下标记录下来
                if (arr[min] > arr[j]) {
                    min = j;
                }
            }
            temp = arr[i];
            arr[i] = arr[min];
            arr[min] = temp;
        } 
package MyTest;

import java.util.Arrays;
import java.util.Random;

public class OrderArray {
    public static void main(String[] args) {
        int[] arr = new int[]{65, 17, 65, 34, 16, 52, 88, 28, 71, 18};
        //fill(arr);
        print(arr);
        println();
        sort(arr);
        print(arr);
    }

    private static void println() {
        System.out.println();
    }

    static void sort(int[] arr) {
        int arrLength = arr.length;
        int temp = 0;
        int min = 0;
        // 假如有5个人需要按照个子高低排序
        // 第一轮假设下标0的元素是最小的
        // 第二轮假设下标1的元素是最小的
        // 第三轮假设下标2的元素是最小的
        // 第四轮假设下标3的元素是最小的,这是最后一轮比较,这次比较只有两个数,下标为3的数是这个数组倒数第2个数,所以i < arrLength-1
        for (int i = 0; i < arrLength - 1; i++) {
            min = i;
            // 把最小的数和第2个数、第3个数、第4个数依次比较...
            for (int j = i + 1; j < arrLength; j++) {
                // 如果还有一个数a比这个数小,就把数a的下标记录下来
                if (arr[min] > arr[j]) {
                    min = j;
                }
            }
            temp = arr[i];
            arr[i] = arr[min];
            arr[min] = temp;
        }
    }

    static void fill(int[] arr) {
        Random random = new Random();
        for (int i = 0; i < 10; i++) {
            int num = random.nextInt(100);
            arr[i] = num;
        }
    }

    static void print(int[] arr) {
        for (int i = 0; i < 10; i++) {
            System.out.print(arr[i] + " | ");
        }
    }
}
View Code

插入排序

效率
插入排序仍需要O(N2)的时间。
比较次数:在第一轮中,比较1次;第二轮中,最多比较2次;第三轮中,最多比较3次...最后一轮,比较N-1次,即1+2+3+...+N-1,也就是N*(N-1)/2,但每一轮平均比较次数是最多比较次数的二分之一,所以平均比较次数是N*(N-1)/4,即N2/4。
复制次数:复制的次数大约等于比较的次数。但复制和交换不同,复制只一步操作,交换要三步操作。
比较:如果是随机数据,这个算法比冒泡排序快一倍,因为冒泡比较次数是N2/2,它的比较次数是N2/4,冒泡的交换次数是N2/4,它的复制次数也是N2/4,并且复制比交换还快;比选择排序略快,因为选择排序的比较次数是N2/2,它的比较次数是N2/4,选择排序交换N次,但它是复制,不是交换。
但如果是逆序数据,每次比较和移动都会执行,所以它并不比冒泡排序快。

应用:对于小规模文件以及基本有序的文件,插入排序能比快速排序更为有效。

算法:插入排序从排序过程的中间开始描述,更容易理解。
假如中部有一个元素是标记元素,那么它左边的元素从低到高都已经排好序了,即局部有序,它以及它右边的元素还没有排好序。
先把标记元素缓存一份。
现在把标记元素和局部有序部分里紧挨着它的元素比较,如果标记元素大,说明标记元素大于局部有序部分所有的元素,什么也不需要做;如果标记元素小,把与之比较的元素复制到下一个位置,接着把标记元素和有序部分倒数第二个元素比较......

        int arrLength = arr.length;
        int temp = 0;
        // 假如有5个人需要按照个子高低排序
        // 第一轮有序部分是下标为1的元素的左边是局部有序部分
        // 第二轮有序部分是下标为2的元素的左边是局部有序部分
        // 第三轮有序部分是下标为3的元素的左边是局部有序部分
        // 第四轮有序部分是下标为4的元素的左边是局部有序部分
        for (int i = 1; i < arrLength; i++) {
            // 把标记元素缓存起来
            temp = arr[i];
            int j = i;
            for (; j > 0 && temp < arr[j - 1]; j--) {
                // 如果缓存起来的数小于和它比较的数a,数a移动到下一个位置
                arr[j] = arr[j - 1];
            }
            // 两种情况需要把缓存起来的数复制回原数组
            // 1、缓存起来的数大于和它比较的数
            // 2、和它比较的数的下标为0的时候,因为下标为0的数已经和它比较过并且此时已经被复制到了下标为1处,下标为0的位置就是专门留给它的
            arr[j] = temp;
        }
package MyTest;

import java.util.Arrays;
import java.util.Random;

public class OrderArray {
    public static void main(String[] args) {
        int[] arr = new int[100];
        fill(arr);
        print(arr);
        println();
        sort(arr);
        print(arr);
    }

    private static void println() {
        System.out.println();
    }

    static void sort(int[] arr) {
        int arrLength = arr.length;
        int temp = 0;
        // 假如有5个人需要按照个子高低排序
        // 第一轮有序部分是下标为1的元素的左边是局部有序部分
        // 第二轮有序部分是下标为2的元素的左边是局部有序部分
        // 第三轮有序部分是下标为3的元素的左边是局部有序部分
        // 第四轮有序部分是下标为4的元素的左边是局部有序部分
        for (int i = 1; i < arrLength; i++) {
            // 把标记元素缓存起来
            temp = arr[i];
            int j = i;
            for (; j > 0 && temp < arr[j - 1]; j--) {
                // 如果缓存起来的数小于和它比较的数a,数a移动到下一个位置
                arr[j] = arr[j - 1];
            }
            // 两种情况需要把缓存起来的数复制回原数组
            // 1、缓存起来的数大于和它比较的数
            // 2、和它比较的数的下标为0的时候,因为下标为0的数已经和它比较过并且此时已经被复制到了下标为1处,下标为0的位置就是专门留给它的
            arr[j] = temp;
        }
    }

    static void fill(int[] arr) {
        Random random = new Random();
        for (int i = 0; i < 100; i++) {
            int num = random.nextInt(100);
            arr[i] = num;
        }
    }

    static void print(int[] arr) {
        for (int i = 0; i < 100; i++) {
            System.out.print(arr[i] + " | ");
        }
    }
}
View Code

表插入排序

利用有序链表可以实现一种高效的排序。

算法:假设有一个无序数组,从这个数组中取出数据,一个个插入有序链表;所有数据都插入有序链表后,再从链表中一个个删除,重新放入数组。

效率

时间复杂度,这种排序比插入排序更快一些。插入排序平均比较N2/4,平均复制N2/4;表插入排序,平均比较N2/4,每个数据项只进行两次复制,一次从数组到链表,一次从链表到数组,所以复制次数是2*N。
空间复杂度,表插入排序的空间复杂度是插入排序的两倍。

原文地址:https://www.cnblogs.com/Mike_Chang/p/10188228.html