常见排序算法原理和代码实现小结

前言

这里总结一下一些常见的排序算法的原理和代码实现,主要有五种排序算法,分别如下:冒泡排序,插入排序,选择排序,归并排序和快速排序。规定排序后的数据都是从小到大。

冒泡排序(Bubble Sort)

原理: 冒泡排序只比较相邻的两个数据,如果左边的数据比右边的数据大,则交换两个数据的位置,否则不用处理。之后继续与后面相邻的数据比较,这样一次遍历之后,最大的数据就到了数组的最尾端。重复 n 次遍历(n 为数据的大小),数组中的数据就是从小到大排序好的。
看图会比较好理解:
在这里插入图片描述
注:图片转载自:极客时间《数据结构与算法之美》
代码实现:

// 冒泡排序,a表示数组,n表示数组大小; 最好情况时间复杂度:o(n), 最坏情况时间复杂度: o(n^2), 平均情况时间复杂度: o(n^2)。
        public int[] bubbleSort(int[] a, int n) {
            if (n <= 1) {
                return a;
            }
            // i 表示遍历数组的下标。
            for (int i = 0; i < n; i++) {
                boolean flag = false;                       // 提前退出冒泡循环的标志位
                for (int j = 0; j < n - i - 1; j++) {       // j 表示比较的次数,
                    if (a[j] > a[j+1]) {                    // 交换数据
                        int temp = a[j];
                        a[j] = a[j+1];
                        a[j+1] = temp;
                        flag = true;
                    }
                }

                if (!flag) {                //表示这一次循环没有数据交换,则表示数据已经是有序的了,循环终止。
                    break;
                }
            }
        return a;
    }

这里我简单说明一下: i 表示我们需要遍历的数组的下标, j 表示需要比较的次数,flag 是一个标志位,如果数据还不是有序的,则一次遍历至少需要交换一次相邻的数据,如果一次遍历完,没有需要交互的相邻数据,则说明数据已经是有序的了,则终止循环,减少代码的比较和交换次数。

时间复杂度: 冒泡排序最好情况时间复杂度:o(n), 最坏情况时间复杂度: o(n^2), 平均情况时间复杂度: o(n^2)。

插入排序(Insertion Sort)

原理: 插入排序的原理是我们把需要排序的数组分成两个区间:已排序区间和未排序区间,在最开始时,已排序区间只有一个元素,就是数组的第一个元素,之后我们遍历未排序区间的数据,依次与已排序区间的数据进行比较,将数据插入到已排序区间的某个位置中,该位置之后的数据依次往后移一位。这样直到未排序区间中的数据为空时,已排序区间中的数据就是有序的数组数据。
看图会比较好理解:
在这里插入图片描述
代码实现:

//插入排序, a表示数组,n表示数组大小;最好情况时间复杂度:o(n), 最坏情况时间复杂度: o(n^2), 平均情况时间复杂度: o(n^2)。
    public int[] insertionSort(int[] a, int n) {
        if (n <= 1) {
            return a;
        }
        // i 表示数组的序号,从第二个数开始。
        for (int i = 1; i < n; i++) {
            int value = a[i];             //当前待插入的数据。
            int j = i - 1;                // 前一个数据序号。

            for (; j >= 0; j--) {
                if (a[j] > value) {        // 如果前一个数据大于当前待插入数据,则将前一个数据后移,j - 1继续与待插入数据进行比较。
                    a[j+1] = a[j];
                } else {
                    break;                // 如果前一个数据小于等于当前待插入数据,则跳出循环。
                }
            }

            a[j+1] = value;        // 将数据插入到指定位置,由于跳出循环时 j 的值已经多减了一次 1,所以这里插入的位置要加 1。
        }
        return a;
    }

代码注释解释的比较清楚了,我就不多赘述了。
时间复杂度:插入排序最好情况时间复杂度:o(n), 最坏情况时间复杂度: o(n^2), 平均情况时间复杂度: o(n^2)。

选择排序(Selection Sort)

原理: 选择排序的原理比较好理解。我们假设数组的第一个数据是最小的,依次遍历之后的数据,与第一个数据进行比较,如果有数据比第一个数据还小,则交换两个数据的位置。这样一次遍历下来,数组中最小的数据就已经是数组中的第一个数据了。之后重复上面的动作,数组中的数据就是有序的了。

看图会比较好理解:
在这里插入图片描述
代码实现:

// 选择排序,a表示数组,n表示数组大小;最好情况时间复杂度:o(n^2), 最坏情况时间复杂度: o(n^2), 平均情况时间复杂度: o(n^2)。
    public int[] selectionSort(int[] a, int n) {
        if (n <= 1) {
            return a;
        }

        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                if (a[j] < a[i]) {
                    int temp = a[i];
                    a[i] = a[j];
                    a[j] = temp;
                }
            }
        }
        return a;
    }

选择排序的代码实现比较简单,但是时间复杂度比较高。
时间复杂度: 最好情况时间复杂度:o(n^2), 最坏情况时间复杂度: o(n^2), 平均情况时间复杂度: o(n^2)。

总结完了三种比较简单的排序算法之后,接下来的两个排序就比较复杂了:归并排序和快速排序。实际工程中用的比较多的也是这两种排序算法。

归并排序(Merge Sort)

原理: 归并排序的原理是把数组从中间分成前后两部分,然后对前后两部分分别排序,再将排好序的两部分合并在一起,这样整个数组就都有序了。
看图看图:
在这里插入图片描述
代码实现:

//归并排序
    public static int[] mergeSort(int[] a, int start, int end){
        int mid = (start + end) / 2;
        if(start < end){
            mergeSort(a, start, mid);
            mergeSort(a,mid + 1, end);
            //左右归并
            merge(a, start, mid, end);
        }
        return a;
    }

    public static void merge(int[] a, int start, int mid, int end) {
        int[] temp = new int[end-start+1];
        int i= start;
        int j = mid+1;
        int k=0;
        // 把较小的数先移到新数组中
        while(i<=mid && j<=end){
            if(a[i]<=a[j]){
                temp[k++] = a[i++];
            }else{
                temp[k++] = a[j++];
            }
        }
        // 把左边剩余的数移入数组
        while(i<=mid){
            temp[k++] = a[i++];
        }
        // 把右边边剩余的数移入数组
        while(j<=end){
            temp[k++] = a[j++];
        }
        // 把新数组中的数覆盖原来的数组a。
        for(int x=0;x<temp.length;x++){
            a[x+start] = temp[x];
        }
    }

时间复杂度: 归并排序的时间复杂度比较稳定,不管是最好情况、最坏情况,还是平均情况,时间复杂度都是 O(nlogn)。但是归并排序有个致命弱点,我们在合并数组的时候需要额外的数组存储空间,这样导致空间复杂度为 O(n)。

快速排序(Quick Sort)

原理:快排的原理是我们首先从数组中选取一个分区点(pivot),分区点可以任意,通常为数组的最后一个元素,然后遍历数组,将小于分区点的数据放到左边,大于分区点的数据放到右边,分区点在中间。根据分治、递归的处理思想,我们可以用递归排序下标从 p 到 q-1 之间的数据和下标从 q+1 到 r 之间的数据,直到区间缩小为 1,就说明所有的数据都有序了。

看图看图:
在这里插入图片描述
代码实现:

 // 快速排序,a是数组,n表示数组的大小
    public static void quickSort(int[] a, int n) {
        quickSortInternally(a, 0, n-1);
    }

    // 快速排序递归函数,p,r为下标
    private static void quickSortInternally(int[] a, int p, int r) {
        if (p >= r) {
            return;
        }

        int q = partition(a, p ,r);   // 获取分区点
        quickSortInternally(a, p, q-1);
        quickSortInternally(a, q+1, r);
    }

    private static int partition(int[] a, int p, int r) {
        int pivot = a[r];
        int i = p;
        for (int j = p; j < r; j++) {
            if (a[j] < pivot) {
                if (i == j) {
                    ++i;
                } else {
                    int tmp = a[i];
                    a[i++] = a[j];
                    a[j] = tmp;
                }
            }
        }

        int tmp = a[i];
        a[i] = a[r];
        a[r] = tmp;

        System.out.println("i=" + i);
        return i;
    }

说明:快排的代码实现有点难理解,我也没理解太多,这里我先把代码实现给贴在这,方便后续查看。
时间复杂度:大部分情况下快排的时间复杂度都是O(nlogn),极端情况下为 O(n2)。不过退化到 O(n2) 的概率非常小,我们可以通过合理地选择 pivot 来避免这种情况。

最后说明:本篇文章只是笔记性质,网上有比这讲的好的多的文章,我贴出来只是方便以后查看各算法的代码实现以及复习。

几种常见排序 基于比较的排序算法: 下界是 nlgn 1.1 SelectionSort:每次选出最下的元素,放在当前循环最左边的位置。 1.2 BubbleSort:每次比较相邻的两个数,使得最大的数像气泡一样冒到最右边。 1. 3 InsertionSort:每次拿起一个数,插入到它左边数组的正确位置。 1.4 QuickSort:选择一个数,作为标准,小于它的放在左边,大于它的放在右边。并把它放在中间;递归地对左右子数组进行排序实现时:1. 确定递归结束条件,初始化左右游标, 选择标准数; 2. while循环,do while实现两个游标同时向中间移动,置换; 3. 置换标准数右边游标所指的数; 4. 递归调用,对左右子数组进行排序。 1. 5 HeapSort:用最大堆实现实现时:建堆:置换堆顶元素最后一个元素,堆大小减少,保持新的堆为最大堆; 保持最大堆: 从底向上依次保持最大堆,从第一个父节点到根部。 1.6 MergeSort:拆分数组,递归实现排序,二路归并。用哨兵来阻止游标的越界。 线性时间运行的算法: 1.7 CountingSort: 假设数据分布在0到k之间的。对于每个输入x,确定出小于x的数的个数。假设小于x的数有17个,那么x就应该在第18个输出位置。 1. 8 Radix sort(基数排序):从最低位开始,每位采用稳定的排序算法(如计数排序)。 1.9 Bucket sort:当输入数据比较均匀时采用。 先将数据区间分为n个桶,把数据分放到对应的桶中;对桶内的数据采用插入排序;再把各个桶的排序结果串起来。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值