zoukankan      html  css  js  c++  java
  • 排序算法

    整理一下常见的排序算法。

    1、插入排序

    插入排序是基础的排序之一,插入排序的过程,脑补打扑克,分成两部分:一部分是手里的牌(已经排好序),一部分是要拿的牌(无序)。这种往一个有序的集合里面插入元素,插入后序列仍然有序这就是插入排序算法思路。

        public static void main(String[] args) {
            int[] a = {3, 43, 32, 2, 4, 6, 23, 9, 8, 2, 6, 7, 43};
            int n = a.length;
            // 无序的部分,从第二个开始,因为第一个是有序的
            for (int i = 1; i < n; i++) {
                int data = a[i];
                int j = i - 1;
                // 有序的部分,从后往前挨个遍历
                for (; j >= 0; j--) {
                    // 如果当前元素大于data,表示data要往前移
                    if (a[j] > data) { 
                        a[j + 1] = a[j];
                    } else {
                        break;
                    }
                }
                a[j + 1] = data;
            }
            System.out.println(Arrays.toString(a));
        }

    输出结果:[2, 2, 3, 4, 6, 6, 7, 8, 9, 23, 32, 43, 43]

    插入排序的时间复杂度是O(n^2),是稳定的,它是最基础的排序算法。如果对插入排序进行优化,我们从上面可以看到就是break的地方越多越好,就表示前面都是有序的,且当前比较元素已经找到了插入的位置。

    2、希尔排序

    希尔排序是插入排序的改良版。把数据下标按照一定的增量分组,对每组使用插入排序,当增量减至1时,排序终止。 其改良的地方尽可能分出来更多的有序段,因为从插入排序可知其对有序段的处理速度是很快的。

    public static void main(String[] args) {
            int[] a = {3, 43, 32, 2, 4, 6, 23, 9, 8, 2, 6, 7, 43};
            int n = a.length;
            // 分组,每次下标按照gap递增
            for (int gap = n / 2; gap >= 1; gap /= 2) {
                // 下面的处理就和插入排序一样了
                for (int i = 1; i < n; i+=gap) {
                    int data = a[i];
                    int j = i - gap;
                    for (; j >= 0; j-=gap) {
                        if (a[j] > data) {
                            a[j + gap] = a[j];
                        } else {
                            break;
                        }
                    }
                    a[j + gap] = data;
                }
            }
            System.out.println(Arrays.toString(a));
        }

    希尔排序能一定程度的提高插入排序的性能,时间复杂度为O(n^2),但是这样分组,中间还会有交换,排序是不稳定的。

    3、归并排序

    归并排序是jdk源码中使用的排序,时间复杂度为O(nlogn),是非常高效的一种排序算法,原理有点类似于二分,分到最后一个,就是有序的,然后再进行合并,写这个代码要用到递归的思想。

    public class MergeSort {
        static int[] a    = {3, 43, 32, 2, 4, 6, 23, 9, 8, 2, 6, 7, 43};
        // 借用数组临时保存数据
        static int[] temp = new int[a.length];
        public static void main(String[] args) {
            mergeSort(a, 0, a.length - 1);
            System.out.println(Arrays.toString(a));
        }
        // 归并排序的核心在于先分后合,数据拆到只剩一个就是有序的,再进行合并
        public static void mergeSort(int[] a, int left, int right) {
            // 终止条件,只有一个数,就不用再分了
            if (left < right) {
                int mid = (left + right) / 2;
                mergeSort(a, left, mid);
                mergeSort(a, mid + 1, right);
                merge(a, left, mid, right);
            }
        }
        // left-mid之间是有序的,mid-right之间是有序的,合并两个有序的数组
        public static void merge(int[] a, int left, int mid, int right) {
            // loc下标用来指向临时数组赋值的位置
            int loc = left;
            // 用来标记左边数组合并到哪个位置
            int p1 = left;
            // 用来标记用边数组合并到哪个位置
            int p2 = mid + 1;
            // 循环条件是左边和右边都没合并完成
            while (p1 <= mid && p2 <= right) {
                // 现在要做的就是左边和右边比较和交换
                if (a[p1] <= a[p2]) {
                    temp[loc++] = a[p1++];
                } else {
                    temp[loc++] = a[p2++];
                }
            }
            // 上面的循环终止了,但是不知道是左边的还是右边的合并完了,需要处理未合并的数据
            while (p1 <= mid) {
                temp[loc++] = a[p1++];
            }
            while (p2 <= right) {
                temp[loc++] = a[p2++];
            }
            // 最后,将临时数组中合并完的有序数据存入原数组
            for (int i = left; i <= right; i++) {
                a[i] = temp[i];
            }
        }
    }

    时间复杂度o(nlogn),是稳定的

    4、选择排序

    选择排序和插入排序很类似,也分有序和无序两部分,不同的思想是插入排序对数据的操作是移动,选择排序是交换,每次都能找到最小的数。

    public static void main(String[] args) {    
            int[] a = {3, 43, 32, 2, 4, 6, 23, 9, 8, 2, 6, 7, 43};
            int n = a.length;
            // 从第一个开始遍历到倒数第二个
            for (int i = 0; i < n - 1; i++) {
                // 一次找到一个最小的值,记录下标
                for (int j = i + 1; j < n; j++) {
                    int index = i;
                    if (a[index] > a[j]) {
                        index = j;
                    }
                    // 将a[i]和最小值交换
                    a[i] = (a[i] + a[index]) - (a[index] = a[i]);
                }
            }
            System.out.println(Arrays.toString(a));
        }

    时间复杂度O(n^2),不稳定

    5、冒泡排序

    每次冒泡操作都会对相邻的两个元素进行比较,不满足大小关系就交换,一次冒泡能让一个元素移动到它应该在的位置,n次冒泡排序完成。

        public static void main(String[] args) {
            int[] a = {3, 43, 32, 2, 4, 6, 23, 9, 8, 2, 6, 7, 43};
            int n = a.length;
            for (int i = 0; i < n - 1; i++) {
                for (int j = i + 1; j < n; j++) {
                    if (a[i] > a[j]) {
                        a[i] = (a[i] + a[j]) - (a[j] = a[i]);
                    }
                }
            }
            System.out.println(Arrays.toString(a));
        }

    冒泡的排序时间复杂度是O(n^2),交换和比较的次数是比较多的,是稳定的

    6、快速排序

    快排的思路是找一个基准数,从后往前找比之小的交换,从前往后找比之大的交换,这样循环处理之后比它小的都在左边,比它大的都在右边,对左右分别递归,完成排序。

        public static void qSort(int[] a, int left, int right) {
            // 定义基准数为第一个数
            int base = a[left];
            // 左指针,从左往右找比之大的数
            int p1 = left;
            // 右指针,从右往左找比之小的数
            int p2 = right;
            // 终止条件,已经找到了同一个位置
            while (p1 < p2) {
                // 左右指针还未指向同一个位置且基准数比右边的小
                while (p1 < p2 && base <= a[p2]) {
                    p2--;
                }
                // 左右指针仍未指向同一个位置,说明上面循环退出的条件是:从右到左找到了比base小的数,进行交换
                if (p1 < p2) {
                    a[p1] = (a[p1] + a[p2]) - (a[p2] = a[p1]);
                    p1++;
                }
                // 和上面类似,这边是从左向右找比基准数大的数进行交换
                while (p1 < p2 && base >= a[p1]) {
                    p1++;
                }
                if (p1 < p2) {
                    a[p1] = (a[p1] + a[p2]) - (a[p2] = a[p1]);
                    p2--;
                }
            }
            // 左半部分递归
            if (left < p1) {
                qSort(a, left, p1 - 1);
            }
            // 右半部分递归
            if (p2 < right) {
                qSort(a, p2 + 1, right);
            }
        }

    快排的时间复杂度是O(nlogn),最坏的情况是O(n^2)。优化快排性能主要从优化基准数方向考虑,比如取三个数计算出合适的基准数。

    快排和归并有些相似处,都会对数据进行拆分,不同的是归并排序从上到下处理,先处理子问题,然后再合并。而快排就是从上到下分区再处理子问题,不用合并,类似于尾递归。

    这么多排序算法,如何选择,首先要看场景,需不需要稳定排序,然后看数据量,小的话直接选插入也没什么问题(虽然它是O(n^2)),然后还要分析空间,归并排序就需要额外开辟部分空间。所有没有说一定适用的排序算法,视情况而定,如果不好分析,选归并或者快排,基本能解决问题。

  • 相关阅读:
    C 语言学习 --3
    C 语言学习 --2
    C 语言学习 -1
    C 语言学习 说明
    神经网络5:循环神经网络1
    神经网络4:卷积神经网络学习 2
    神经网络3:神经网络学习 1
    神经网络2:卷积神经网络学习 1
    神经网络 1 : 卷积神经网络CNN 、 循环神经网络 RNN
    轮播swiper配置选项
  • 原文地址:https://www.cnblogs.com/dlcode/p/14130191.html
Copyright © 2011-2022 走看看