部分常见的排序算法:

  • 1、冒泡排序
  • 1.1、排序图解
  • 1.2、代码体现
  • 2、选择排序
  • 2.1、排序图解
  • 2.2、代码体现
  • 3、插入排序
  • 3.1、排序图解
  • 3.2、代码体现
  • 4、希尔排序
  • 4.1、排序图解
  • 4.2、代码体现
  • 5、快速排序
  • 5.1、排序图解
  • 5.2、代码体现
  • 6、归并排序
  • 6.1、排序图解
  • 6.2、代码体现
  • 7、基数排序
  • 7.1、排序图解
  • 7.2、代码体现
  • 7.3、关于基数排序的一些说明
  • 8、以上排序算法的总结和对比


1、冒泡排序

冒泡排序的基本思想是:依次比较相邻元素的值,若发现逆序则交换,使较大的元素逐渐向后移动,就像是水底的气泡一样逐渐向上浮动。

1.1、排序图解

java中常用的算法 java工作中常用的算法_数组

1.2、代码体现

public class BubbleSort {
    public static void main(String[] args) {
        int value[] = {3, -6, 18, 5, -11};
        sort(value);
        System.out.println("排序后 : " + Arrays.toString(value));
    }

    private static void sort(int[] value) {
        int temp;//定义一个临时变量,在交换位置的时候使用
        boolean flag = false;//定义一个标识,用于判断是否进行过交换,从而优化当前排序算法
        for (int i = 1; i < value.length; i++) { //外层for循环控制循环的次数
            for (int j = 0; j < value.length - i; j++) { //内存for循环控制比较的次数
                if (value[j] > value[j + 1]) { //如果前一位数大于后一位,则交换它们的位置
                    flag = true;//说明此时有发生位置交换,将标识更改为true
                    temp = value[j + 1];//将较小值赋值给临时变量
                    value[j + 1] = value[j];//将较大值后移
                    value[j] = temp;//将较小值前移
                }
            }
            //这里将其进行一个简单的优化:说明在某一次排序中,未进行任何的位置交换,也就表明当前的顺序已经是有序的了,从而减少不必要的比较
            if (!flag) {
                break;
            } else {
                flag = false;//将标识还原,便于后续继续使用
            }
        }
    }
}

2、选择排序

选择排序的基本思想是:第一次从arr[0 ]— arr[n-1]中选取最小值,与arr[0]交换;第二次从arr[1] — arr[n-1]中选取最小值,与arr[1]交换;第三次从arr[2] — arr[n-1]中选取最小值中选取最小值,与arr[2]交换…以此类推,工通过n-1次后,得到一个有序序列。

2.1、排序图解

java中常用的算法 java工作中常用的算法_java中常用的算法_02

2.2、代码体现

//选择排序
public class SelectSort {
    public static void main(String[] args) {
        int[] value = {3, -6, 18, 5, -11};
        sort(value);
        System.out.println("排序后 : " + Arrays.toString(value));
    }

    private static void sort(int[] value) {
        int minIndex;//假定的最小下标
        int minValue;//假定的最小值
        for (int i = 0; i < value.length - 1; i++) { //外层for循环控制循环的次数
            minIndex = i;
            minValue = value[i];
            for (int j = i + 1; j < value.length; j++) { //内层for循环控制比较的次数
                if (minValue > value[j]) { //说明假定的最小值并不是最小的
                    minValue = value[j];//将这个最小值重置
                    minIndex = j;//重置最小下标
                }
            }
            //如果当前下标和假定的最小下标不相等,则说明需要交换这两个值
            if (minIndex != i) {
                value[minIndex] = value[i];
                value[i] = minValue;
            }
        }
    }
}

3、插入排序

插入排序的基本思想是:把n个待排元素看成一个有序表和一个无序表,开始的时候有序表中只有一个元素,无序表中有n-1个元素,排序过程中每次从无序表中取出第一个元素,将它的排序码依次和有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。

3.1、排序图解

java中常用的算法 java工作中常用的算法_排序算法_03

3.2、代码体现

//插入排序
public class InsertSort {
    public static void main(String[] args) {
        int[] value = {3, -6, 18, 5, -11};
        sort(value);
        System.out.println("排序后 : " + Arrays.toString(value));
    }

    private static void sort(int[] value) {
        int insertVal;//定义待插入的数
        int insertIndex;//定义待插入的下标,即当前元素的前一个元素的下标
        for (int i = 1; i < value.length; i++) { //下标i从1开始,因为是从第二个数开始插入的
            insertVal = value[i];
            insertIndex = i - 1;
            //insertIndex >= 0 其作用是保证在给insertVal找插入位置的时候不会发生下标越界
            //insertVal < value[insertIndex] 其作用是判断插入的数是不是小于前一个数
            while (insertIndex >= 0 && insertVal < value[insertIndex]) {
                //此时说明要插入的数的前一个数大于它,所以需要将这个大数后移
                value[insertIndex + 1] = value[insertIndex];
                insertIndex--;//下标后移,进行后续操作
            }
            //当退出循环时,表名要插入的位置已经找到,此时需要将insertIndex + 1后移
            //因为待插入元素本身就有可能在当前位置,所以先判断一下是否需要赋值
            if (insertIndex + 1 != i) {
                value[insertIndex + 1] = insertVal;//将待插入的数赋值到该位置
            }
        }
    }
}

4、希尔排序

希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序。
其基本思想是:把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止

4.1、排序图解

java中常用的算法 java工作中常用的算法_数组_04


java中常用的算法 java工作中常用的算法_java_05

4.2、代码体现

在进行排序时,有两种方法可供选择:交换法和移动法,如下所示

//希尔排序
public class ShellSort {
    public static void main(String[] args) {
        int[] value = {-3, 6, 8, -5, 0, -11};
        //sort_1(value);
        //System.out.println("方法一排序后 : " + Arrays.toString(value));
        sort_2(value);
        System.out.println("方法二排序后 : " + Arrays.toString(value));
    }

    //方法一:在插入时采用交换法(该方法过于耗时,推荐使用第二种方式:移动法)
    private static void sort_1(int[] value) {
        int temp;//定义一个临时变量,作交换时使用
        for (int gap = value.length / 2; gap > 0; gap /= 2) { //gap:步长。外层for控制循环次数
            for (int i = gap; i < value.length; i++) { //当前for控制每次比较的次数
                for (int j = i - gap; j >= 0; j -= gap) {
                    //如果当前元素大于加上步长后的那个元素,则进行交换
                    if (value[j] > value[j + gap]) {
                        temp = value[j];
                        value[j] = value[j + gap];
                        value[j + gap] = temp;
                    }
                }
            }
        }
    }

    //方法二:在插入时采用移动法,其本质就是结合了插入排序的思想
    private static void sort_2(int[] value) {
        for (int gap = value.length / 2; gap > 0; gap /= 2) {
            //直接从gap个元素开始,逐个对其所在的组进行直接插入排序
            for (int i = gap; i < value.length; i++) {
                int j = i;//将当前下标先临时记录一下
                int temp = value[j];//将当前值也一并记录下来
                //j - gap其作用是为了防止下标越界
                while (j - gap >= 0 && temp < value[j - gap]) {
                    //temp < value[j - gap]:如果当前值小于所在组的另一个元素时,将其位置对换
                    value[j] = value[j - gap];
                    j -= gap;//拿到要插入的位置下标
                }
                //当退出while循环时,此时temp就找到了要插入的位置
                value[j] = temp;
            }
        }
    }
}

5、快速排序

快速排序是对冒泡排序的一种改进。基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列

5.1、排序图解

java中常用的算法 java工作中常用的算法_java中常用的算法_06

5.2、代码体现

//快速排序
public class QuickSort {
    public static void main(String[] args) {
        int[] value = {3, -6, 18, 5, -11};
        sort(value, 0, value.length - 1);
        System.out.println("排序后 : " + Arrays.toString(value));
    }

    /**
     * 将给定的数组进行有序的排序
     * @param value 要进行排序的数组
     * @param left 左索引
     * @param right 右索引
     */
    private static void sort(int[] value, int left, int right) {
        int l = left;
        int r = right;
        //pivot是用来做分割的数
        int pivot = value[(l + r) / 2];
        int temp;//定义一个临时变量,在做交换的时候使用
        //while循环的目的是为了让比pivot值小的放到左边,比pivot值大的放到右边
        while (l < r) {
            //在pivot的左边一直找,找到大于等于pivot的值才退出
            while (value[l] < pivot) {
                l++;
            }
            //在pivot的右边一直找,找到小于等于pivot的值才退出
            while (value[r] > pivot) {
                r--;
            }
            //如果l>=r说明pivot的左右两边的值都已经按照:左边全部都是小于等于pivot的值右边全部都是大于等于pivot的值放好了
            if (l >= r) {
                break;
            }
            //交换
            temp = value[l];
            value[l] = value[r];
            value[r] = temp;
            //如果交换完之后,发现arr[l] == pivot,则r--,向前移一步,避免造成死循环
            if (value[l] == pivot) {
                r--;
            }
            //如果交换完之后,发现arr[r] == pivot,则l++,向后走一步,同样是为了避免造成死循环
            if (value[r] == pivot) {
                l++;
            }
        }

        //如果l==r,必须l++,r--,否则会出现栈溢出
        if (l == r) {
            l++;
            r--;
        }
        //向左递归,将pivot左边的数进行有序处理
        if (left < r) {
            sort(value, left, r);
        }
        //向右递归,将pivot右边的数进行有序处理
        if (right > l) {
            sort(value, l, right);
        }
    }
}

6、归并排序

归并排序是利用归并的思想实现的排序方法,该算法采用经典的分治策略(分治法将问题分成一些小的问题然后递归求解,而治的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。

6.1、排序图解

java中常用的算法 java工作中常用的算法_数组_07

6.2、代码体现

//归并排序
public class MergeSort {
    public static void main(String[] args) {
        int[] value = {3, -6, 18, 5, -11};
        int[] temp = new int[value.length];//归并排序需要一个额外空间
        sort(value, 0, value.length - 1, temp);
        System.out.println("排序后 : " + Arrays.toString(value));
    }

    private static void sort(int[] value, int left, int right, int[] temp) {
        if (left < right) {
            int mid = (left + right) / 2;//拿到中间索引
            //向左递归进行分解
            sort(value, left, mid, temp);//left即为左边序列起始下标,mind即为最终下标
            //向右递归进行分解
            sort(value, mid + 1, right, temp);//mind+1即为右边序列起始下标,right即为最终下标
            //合并
            merge(value, left, mid, right, temp);
        }
    }

    /**
     * 用于对上一步分解的结果进行合并
     *
     * @param value 原始数据
     * @param left  左边有序序列的索引
     * @param mid  中间索引
     * @param right 右边有序序列的索引
     * @param temp  做中转的数组
     */
    public static void merge(int[] value, int left, int mid, int right, int[] temp) {
        int l = left;//初始化l,即左边有序序列的初始索引
        int r = mid + 1;//初始化r,即右边有序序列的初始索引
        int index = 0;//指向temp数组的当前索引
        //1、先把左右两边的有序数据按照规则填充到temp数组中,直到左右两边的有序序列,有一边处理完毕为止
        while (l <= mid && r <= right) {
            //如果左边的有序序列的当前元素,小于等于右边有序序列的当前元素,就将左边的当前元素,拷到temp数组中
            if (value[l] <= value[r]) {
                temp[index] = value[l];//将左边的当前元素,拷到temp数组中
                index++;//将temp的当前索引前移
                l++;//左边序列索引前移
            } else { //反之,将右边有序序列的当前元素拷到temp中
                temp[index] = value[r];//将右边的当前元素,拷到temp数组中
                index++;//将temp的当前索引前移
                r++;//右边序列索引前移
            }
        }
        //2、把剩余数据的一边的数据按顺序依次填充到temp中
        while (l <= mid) {
            //说明左边的有序序列中还有剩余的元素,将其填充到temp中
            temp[index] = value[l];
            index++;
            l++;
        }
        while (r <= right) {
            //说明右边的有序序列中还有剩余的元素,将其填充到temp中
            temp[index] = value[r];
            index++;
            r++;
        }
        //3、将temp数组的元素拷贝到原始数组value中。此时需要注意的是:并不是每次都会将所有的数据都会拷贝过去
        index = 0;
        int tempLeft = left;
        while (tempLeft <= right) {
            value[tempLeft] = temp[index];
            index++;
            tempLeft++;
        }
    }
}

7、基数排序

基数排序属于“分配式排序”,又称“桶子法”,顾名思义,它是通过键值的各个位的值,将要排序的元素分配至某些“桶”中,达到排序的作用 ;
基数排序法是属于稳定性的排序,基数排序法的是效率高的稳定性排序法 ;
基数排序是桶排序的扩展
基数排序的基本思想:将整数按位数切割成不同的数字,然后按每个位数分别比较。将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。

7.1、排序图解

java中常用的算法 java工作中常用的算法_排序算法_08


java中常用的算法 java工作中常用的算法_数据结构_09

7.2、代码体现

//基数排序
public class RadixSort {
    public static void main(String[] args) {
        int[] value = {15, 8, 121, 99, 0, 9,1};
        sort(value);
        System.out.println("排序后 : " + Arrays.toString(value));
    }

    private static void sort(int[] value) {
        //首先得到数组中最大的数的位数
        int max = value[0];//假设第一个数就是最大的数
        for (int i = 1; i < value.length; i++) {
            if (value[i] > max) {
                max = value[i];//找到实际的最大数
            }
        }
        //判断当前最大数是几位数,它决定了我们的排序算法最终循环的次数
        int maxLength = (max + "").length();
        /*这里定义一个二维数组,表示10个桶,每个桶就是一个一位数组。为了防止在放入数据的时候,出现溢出现象
        则将每一个一位数组大小定为value.length,很明显,基数排序用到的就是空间换时间这一思路*/
        int[][] bucket = new int[10][value.length];
        /*为了记录每个桶中,实际存放了多少个数据,还需要定义一个一位数组,来记录各个桶每次放入的数据个数*/
        int[] bucketElementCounts = new int[10];//eg:bucketElementCounts[0],记录的就是第一个桶中放入的数据个数
        //开始对数据进行排序处理,这里n的作用是为了取出每个数的各个位上的数字时需要用到的
        for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {
            //对每个元素的对应位进行排序处理,第一次是个位,第二次是十位,以此类推.....
            for (int j = 0; j < value.length; j++) {
                int digitOfElement = value[j] / n % 10;//取出的每个元素对应位的值
                bucket[digitOfElement][bucketElementCounts[digitOfElement]] = value[j];//将该值放入对应的桶中
                bucketElementCounts[digitOfElement]++;//记录当前桶中的数据个数
            }
            int index = 0;//在放回原数组时需要用到的下标
            //遍历每一个桶,并将桶中的数据,放入到原数组中
            for (int k = 0; k < bucketElementCounts.length; k++) {
                //如果桶中有数据,我们才放入到原数组
                if (bucketElementCounts[k] != 0) {
                    //循环这个桶(即第k个一维数组)
                    for (int l = 0; l < bucketElementCounts[k]; l++) {
                        //取出当前桶中的数据,放入原数组去
                        value[index] = bucket[k][l];
                        index++;//下标后移
                    }
                }
                //每一轮处理结束后,需要将当前bucketElementCounts[k]置零,便于后续继续计数使用
                bucketElementCounts[k] = 0;
            }
        }
    }
}

7.3、关于基数排序的一些说明

1、基数排序是对传统桶排序的扩展,速度很快;
2、 基数排序是经典的空间换时间的方式,占用内存很大, 当对海量数据排序时,容易造成 OutOfMemoryError ;
3、 基数排序是稳定的。[注:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的] ;
4、当前基数排序不支持含有负数的数组,如果需要支持负数操作,请参考其它博文。

8、以上排序算法的总结和对比

java中常用的算法 java工作中常用的算法_数据结构_10

相关术语解释:
1、稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面;
2、不稳定:如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面;
3、内排序:所有排序操作都在内存中完成;
4、外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行;
5、时间复杂度: 一个算法执行所耗费的时间。
6、空间复杂度:运行完一个程序所需内存的大小。
7、 n: 数据规模
8、 k: “桶”的个数