八大排序算法

​ 八大排序算法是面试经常考到的,尤其是快排,希尔排序和归并也是经常会让写代码的题目,其实只要用一句话说明了他们的原理我们写起代码就没那么困难。

冒泡排序

思想:有 n 个数我们就进行 n-1 趟排序,每一趟我们都选取最大的一个数放到已经排序的位置即可。

伪代码:两个 For 循环,外层表示要进行的趟数,内层则是找出最大的数,找最大的数的方法就是比较、交换。

时间复杂度:O(n2)

空间复杂度:O(n)

代码:

package Sorting;

import org.junit.jupiter.api.Test;

import java.util.Arrays;

public class Bubble {

    public static void sort(int[] arr) {
        for (int i=1;i<arr.length-1;++i) {
            for(int j=0;j<arr.length-i;++j) {
                if (arr[j] > arr[j + 1]) {
                    swap(arr,j);
                }
            }
        }
    }

    private static void swap(int[] arr, int j) {
        int temp = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = temp;
    }

    @Test
    public void test() {
        int[] arr = new int[]{2, 1, 4, 5, 3, 2};
        sort(arr);
        System.out.println(Arrays.toString(arr));
    }
}

插入排序

思想:插入排序就是把元素分成两部分,一部分就是有序的,而另外一部分无序。我们每次在无序的集合里面找到一个元素插入到他在有序集合中对应的位置。因此总的来说他就是把一个数据插入到已经排好序的数据中。

####分类:实际上插入排序是一个类别,它里面有很多具体的排序方式。直接插入排序,折半插入排序,希尔排序 ( 又称缩小增量排序 )。他们都是属于稳定排序。

直接插入排序

思想:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列。形象的可以理解为打扑克抓拍的过程,通常我们右手抓牌,没抓一张牌,就放到左手,抓下一张牌后,会把这张牌依次与左手上的牌比较,并把它插入到一个合适的位置(按牌面大小)。

应用优势:插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率。

时间复杂度:O(n2)

package Sorting;

import org.junit.jupiter.api.Test;

import java.util.Arrays;

public class SimpleInsert {
    public void sort(int[] arr) {
        for (int i = 1; i < arr.length; i++) {
            int temp = arr[i];
            int j = i - 1;
            while (j > -1 && temp < arr[j]) {
                arr[j + 1] = arr[j];
                --j;
            }
            arr[j + 1] = temp;
        }
    }

    @Test
    public void test(){
        int[] arr = new int[]{5, 3, 2, 6, 8, 1, 3, 0};
        sort(arr);
        System.out.println(Arrays.toString(arr));
    }
}

二分插入排序

思想:也就是和上面的简单插入排序一样,只是简单插入排序做了很多的判断,元素到底应该插入哪个位置,而这里我们是使用这般查找来确定位置,因为那一部分是有序的。

时间复杂度:n2

希尔排序

思想:希尔排序的基本思想就是设置一个步长,首先步长是整个元素的长度的一半,这样我们每一个分组只有两个元素在这个分组中我们进行排序,然后我们减小步长也就是再次除以二,继续上面的步骤,最后我们会得到整个元素的集合成为一个分组。

稳定性:不稳定

时间复杂度:n(logn)

package Sorting;

import org.junit.jupiter.api.Test;

import java.util.Arrays;

public class Shell {
    public void sort(int[] arr) {
        int step = arr.length / 2;
        //这是循环每一个步长
        while (step > 0) {
            //这是循环每一个分组的第一个元素的下标
            for (int i = 0; i <= step; i++) {
                //这是一层简单插入排序
                for (int ptr = i; ptr + step < arr.length; ptr += step) {
                    int temp = arr[ptr + step];
                    int j = ptr;
                    while (j >= 0 && arr[j] > temp) {
                        arr[j + step] = arr[j];
                        j -= step;
                    }
                    arr[j+step] = temp;
                }
            }
            step /= 2;
        }
    }

    @Test
    public void test(){
        int[] arr = new int[]{5, 3, 2, 6, 8, 1, 3, 0};
        sort(arr);
        System.out.println(Arrays.toString(arr));
    }
}

简单选择排序

思想:始终选取最小的元素往前面的有序序列中插入

稳定性:不稳定

时间复杂度:O(n2)

package Sorting;

import org.junit.jupiter.api.Test;

public class SimpleSorting {
    void sort(int[] arr) {
        int min;
        for (int i = 0; i < arr.length; i++) {
            min = minIndex(arr, i);
            int temp = arr[min];
            arr[min] = arr[i];
            arr[i] = temp;
        }
    }

    private int minIndex(int[] arr, int start) {
        int min = start;
        for (int i = start; i < arr.length; i++) {
            min = arr[i] < arr[min] ? i : min;
        }
        return min;
    }

    @Test
    public void fun() {
        int[] arr = new int[]{6, 2, 3, 4, 51, 32, 52, 33};
        sort(arr);
        for (int ele : arr) {
            System.out.printf(ele + " ");
        }
    }
}

快速排序

思想:寻找一个枢轴元素,然后我们把枢轴元素归位,也就是比他小的放在他的左边比他大的放右边,然后继续在他的左边右边选取枢轴元素继续如此进行。

时间复杂度:

稳定性:这就是在我们输入数据基本有序甚至完全有序的时候,这算法退化为冒泡排序,不再是O(n㏒n),而是O(n^2)了。

package Sorting;

import org.junit.jupiter.api.Test;

public class QuickSorting {

    void sort(int[] arr, int start, int end) {
        if (start >= end) {
            return;
        }
        int center = getMid(arr, start, end);
        sort(arr, start, center - 1);
        sort(arr, center + 1, end);
    }

    void sort(int[] arr) {
        sort(arr, 0, arr.length - 1);
    }

    int getMid(int[] arr, int start, int end) {
        int temp = arr[start];
        while (start < end) {
            while (start < end && arr[end] >= temp) {
                end--;
            }
            arr[start] = arr[end];


            while (start < end && arr[start] <= temp) {
                start++;
            }
            arr[end] = arr[start];
        }
        arr[start] = temp;
        return start;
    }

    @Test
    public void fun() {
        int[] arr = new int[]{5, 3, 2, 6, 8, 1, 3, 0};
        sort(arr);
        for (int ele : arr) {
            System.out.printf(ele + " ");
        }
    }
}

归并排序

思想:就是先把元素分成两部分,然后对这两部分继续排序,最后进行合并这两部分。这也是一个重要的分治思想归并法。

分治思想:经典的分治(divide-and-conquer)策略(分治法将问题(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案”修补”在一起,即分而治之)。

时间复杂度:O(nlogn)

空间复杂度:O(n)

稳定性:稳定

package Sorting;

import org.junit.jupiter.api.Test;

import java.util.Arrays;

public class Merge {
    public static void sort(int []arr){
        int []temp = new int[arr.length];//在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间
        sort(arr,0,arr.length-1,temp);
    }
    private static void sort(int[] arr,int left,int right,int []temp){
        if(left<right){
            int mid = (left+right)/2;
            sort(arr,left,mid,temp);//左边归并排序,使得左子序列有序
            sort(arr,mid+1,right,temp);//右边归并排序,使得右子序列有序
            merge(arr,left,mid,right,temp);//将两个有序子数组合并操作
        }
    }
    private static void merge(int[] arr,int left,int mid,int right,int[] temp){
        int i = left;//左序列指针
        int j = mid+1;//右序列指针
        int t = 0;//临时数组指针
        while (i<=mid && j<=right){
            if(arr[i]<=arr[j]){
                temp[t++] = arr[i++];
            }else {
                temp[t++] = arr[j++];
            }
        }
        while(i<=mid){//将左边剩余元素填充进temp中
            temp[t++] = arr[i++];
        }
        while(j<=right){//将右序列剩余元素填充进temp中
            temp[t++] = arr[j++];
        }
        t = 0;
        //将temp中的元素全部拷贝到原数组中
        while(left <= right){
            arr[left++] = temp[t++];
        }
    }

    @Test
    public void fun() {
        int[] arr = new int[]{5, 3, 2, 6, 8, 1, 3, 0, 9};
        int[] temp = new int[arr.length];
        sort(arr, 0, arr.length - 1, temp);
        System.out.println(Arrays.toString(arr));
    }
}

堆排序

思想:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了

稳定性:不稳定

时间复杂度:O(nlogn)

package Sorting;

import org.junit.jupiter.api.Test;

import java.util.Arrays;

public class HeapSort {

    public static void sort(int []arr){
        //1.构建大顶堆
        for(int i=arr.length/2-1;i>=0;i--){
            //从第一个非叶子结点从下至上,从右至左调整结构
            swim(arr,i,arr.length);
        }
        //2.调整堆结构+交换堆顶元素与末尾元素
        for(int j=arr.length-1;j>0;j--){
            swap(arr,0,j);//将堆顶元素与末尾元素进行交换
            swim(arr,0,j);//重新对堆进行调整
        }

    }

    /**
     * 调整大顶堆(仅是调整过程,建立在大顶堆已构建的基础上)
     * @param arr
     * @param i
     * @param length
     */
    public static void swim(int []arr,int i,int length){
        int temp = arr[i];//先取出当前元素i
        for(int k=i*2+1;k<length;k=k*2+1){//从i结点的左子结点开始,也就是2i+1处开始
            if(k+1<length && arr[k]<arr[k+1]){//如果左子结点小于右子结点,k指向右子结点
                k++;
            }
            if(arr[k] >temp){//如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)
                arr[i] = arr[k];
                i = k;
            }else{
                break;
            }
        }
        arr[i] = temp;//将temp值放到最终的位置
    }

    /**
     * 交换元素
     * @param arr
     * @param a
     * @param b
     */
    public static void swap(int []arr,int a ,int b){
        int temp=arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }

    @Test
    public void fun() {
        int[] arr = new int[]{5, 3, 2, 6, 8, 1, 3, 0};
        sort(arr);
        for (int ele : arr) {
            System.out.printf(ele + " ");
        }
    }

}

总结

稳定性:堆排序、快速排序、希尔排序、直接选择排序 不是稳定的排序算法,而基数排序、冒泡排序、直接插入排序、折半插入排序、归并排序是稳定的排序算法。

应用场景:

(1)若n较小(如n≤50),可采用直接插入或直接选择排序。  当记录规模较小时,直接插入排序较好;否则因为直接选择移动的记录数少于直接插人,应选直接选择排序为宜。 (2)若文件初始状态基本有序(指正序),则应选用直接插人、冒泡或随机的快速排序为宜; (3)若n较大,则应采用时间复杂度为O(nlgn)的排序方法:快速排序、堆排序或归并排序。  快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;  堆排序所需的辅助空间少于快速排序,并且不会出现快速排序可能出现的最坏情况。这两种排序都是不稳定的。  若要求排序稳定,则可选用归并排序。但前面介绍的从单个记录起进行两两归并的排序算法并不值得提倡,通常可以将它和直接插入排序结合在一起使用。先利用直接插入排序求得较长的有序子序列,然后再两两归并之。因为直接插入排序是稳定 的,所以改进后的归并排序仍是稳定的。

复杂度:

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 数据结构Queue

    ​ 栈和队列其实是相同的,只是名字不一样 入栈换成了入队(enqueue),出栈换成了出队(dequeue)。语义 是不同的。入队操作向队尾添加元素,而出...

    lwen
  • 优先队列

    优先队列基本介绍 ​ 优先队列又叫做堆,他是一种比较特殊的完全二叉树。所谓完全二叉树就是一层层的堆叠,本层不满就不能堆放下一层。并且优先队列还有一个特点就...

    lwen
  • ArrayList 源码分析

    ArrayList 源码分析 1. 在阅读源码时做了大量的注释,并且做了一些测试分析源码内的执行流程,由于博客篇幅有限,并且代码阅读起来没有 IDE 方便,所...

    lwen
  • C语言实现插入排序

    插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应...

    仙士可
  • 深入浅出的排序算法-选择排序

    选择排序是第一个人和后续排序的人进行比较,若第一个人大于第二个人,就进行交换,那么这时第一人就是最小的,然后这时的第一个人和第三个人进行比较,若这时的第一个人大...

    达达前端
  • 各种排序算法总结

    排序算法是最基本最常用的算法,不同的排序算法在不同的场景或应用中会有不同的表现,我们需要对各种排序算法熟练才能将它们应用到实际当中,才能更好地发挥它们的优势。今...

    前朝楚水
  • 团体程序设计天梯赛-练习集 L1-010 比较大小

    C you again 的博客
  • [数据结构与算法] 排序算法之冒泡排序与快速排序(快排)

    时间静止不是简史
  • 团体程序设计天梯赛-练习集 L1-056 猜数字

    一群人坐在一起,每人猜一个 100 以内的数,谁的数字最接近大家平均数的一半就赢。本题就要求你找出其中的赢家。

    C you again 的博客
  • 快速排序与寻找第k小的数算法

    慕课网 首发了,放在垂直领域吧。简书备份。 出现了一点小问题,就是index,要注意。想法网上一大堆,不多说了。 ubuntu18下输入法有问题,sogou...

    东风冷雪

作者介绍

精选专题

活动推荐

扫码关注云+社区

领取腾讯云代金券