【数据结构】常见的七种排序(冒泡、插入、希尔、选择、堆、快速、归并)

目录

冒泡排序(bubbleSort)

直接插入排序(insertSort)

希尔排序(shellSort)

选择排序(selectSort)

堆排序(heapSort)

快速排序(quickSort)

归并排序(mergeSort)


  • 冒泡排序(bubbleSort)

         原理:从前往后,相邻的两个元素进行比较,满足升序或降序;(这里以升序为例)一次冒泡的   过程,至少可以保证把一个元素放到正确的位置;再升序的前提下,则每次冒泡是将大的元素往后移动。

        下面为一次冒泡的过程

        总共经历5次冒泡,即可将整个序列变为升序

         代码如下:

 public static void bubbleSort(long[] array){
        for (int i = 0; i < array.length - 1; i++) {
            boolean sorted=true;
            for (int j = 0; j < array.length - i - 1; j++) {
                if(array[j]>array[j+1]){
                    sorted=false;
                    swap(array,j,j+1);
                }
            }
            if(sorted){
                return;
            }
        }
    }

  • 直接插入排序(insertSort)

        原理:将一个序列 的第一个元素看做有序,将剩下的元素看做一个无序区间;然后从无须区间的第一个元素开始与有序区间的元素依次向前比较,若比前面的元素小,就将比他大的元素整体向后移动一位,再将该元素插入到正确的位置,则有序区间元素逐渐增加,无序区间元素逐渐减少,直到无序区间不存在任何元素,此时整个序列变为一个升序序列。

下面为直接插入排序的动画演示:

         代码如下:

public static void insertSort(long[] array){
        // 一共要取多少个元素来进行插入过程(无序区间里有多少个元素)
        for (int i = 0; i < array.length - 1; i++) {
            // 有序区间 [0, i]  至少在 i == 0 的时候得有一个元素
            // 无序区间 [i + 1, n)

            // 先取出无序区间的第一个元素,记为 k
            long k = array[i + 1];

            // 从后往前,遍历有序区间{
            // 找到合适的位置退出
            // 所谓合适的位置,就是第一次 k >= array[j] 的位置
            int j;
            for (j = i; j >= 0 && k < array[j]; j--) {
                array[j + 1] = array[j];        // 将不符合条件的数据往后搬一格
            }

            array[j + 1] = k;
        }
    }

  • 希尔排序(shellSort)

        原理:希尔排序可以理解为插入排序的升级版(分组插入排序),也称为缩小增量排序。

将整个区间分为若干个区间(由相隔某个“增量”的元素组成),然后对这些子序列分别进行插入排序。完成后缩小增量的值,此此时又会形成若干个区间,重复之前的操作,直到增量为1时,对整个序列来一次插入排序,而此时的插入排序效率最高,因为整个序列已经有了基本的顺序,达到了一种有序的状态。

        通常用gap表示增量,当一个序列有n个元素时,进行第一次排序时gap=n/2,之后gap=gap/2.

        例:{4,2,6,9,1,3,5,8}

        此时gap=8/2=4,可以分为四组{4,1}、{2,3}、{6,5}、{9,8}

        进行组内排序形成一个新的序列{1,2,5,8,4,3,6,9},此时gap=gap/2=2,可以将序列分成两组

{1,5,4,6}、{2,8,3,9}

        经过排序后又会形成一个新的序列{1,2,4,3,5,8,6,9},此时gap=2/2=1,则一整个序列为一组,但整个序列看上去已经显得部分有序,最后再进行一次插入排序,就能将整个序列变得有序。

得到的有序序列为:{1,2,3,4,5,6,8,9}。

        代码如下:

 public static void shellSort(int[] arr) {
        int j, temp;
        for (int gap = arr.length / 2; gap > 0; gap /= 2) {    //每一次的增量
            for (int i = gap; i < arr.length; i++) {    //从下标为增量的元素开始向后遍历
                temp = arr[i];    //提前保存 arr[i],否则会将其覆盖
                for (j = i - gap; j >= 0; j -= gap) {    //寻找插入位置
                    if (temp < arr[j]) {    //满足条件进行下一步
                        arr[j + gap] = arr[j];    //将元素后移
                    } else {
                        break;
                    }
                }
                arr[j + gap] = temp;    //插入 arr[i]
            }
        }
    }

  • 选择排序(selectSort)

        原理:首先将整个序列看做一个无序区间,将无序区间的第一个元素a0看做最小的,然后和后面的元素一次进行比较,若发现比a0还小的元素,就将这个元素看做最小的,继续和后面比较,若没有比该元素还小的元素,就将该元素和无序区间的第一个元素交换,此时有序区间便有了一个元素。然后重复上述操作,直到整个序列成为有序。

        动画演示:

        代码如下:

public static void selectSort(int[] array) {
        for (int i = 0; i < array.length; i++) {
            int minIndex = i;
            for (int j = i; j < array.length; j++) {
                if (array[j] < array[minIndex]) {
                    minIndex = j;
                }
            }
            int temp = array[minIndex];
            array[minIndex] = array[i];
            array[i] = temp;
        }
    }

  • 堆排序(heapSort)

        原理:基本原理也是选择排序,只不过不需用使用便利的方式来查找最大元素,而是通过堆的性质来找无序区间的最大数。如果升序排列就需要构建大堆,降序排列就要构建小堆。

        基本步骤

        1.首先将无序区间构建成一个大根堆,这时整个堆顶元素就是最大的元素

        2.将堆顶的元素与最后一个元素交换,此时最后一个元素就是最大的数,无序区间的元素会变成n-1个,再将无序区间进行向下调整

        3.反复进行第2步,直到整个序列变得有序

        举例:给定一个序列 {4,2,6,9,1,3,5,8}

        第一步:构建大根堆,形成新的序列{9,8,5,6,1,3,4,2}

        第二步,交换堆顶元素和最后一个元素,并对无序区间进行向下调整

        

         第三步,重复第二步的操作

           

         

         

         最后只有一个结点,排序完成,最终序列为{1,2,3,4,5,6,8,9}

         注意:上述过程中每次交换完成后,最后一个元素并未在堆中呈现,但是在整个序列中是存在的,只不过由于是有序区间,所以不必在堆中呈现

        代码如下:

         

public static void heapSort(long[] array) {
        // 1. 建立大堆
        createBigHeap(array);

        // 2. 遍历 n - 1 次
        for (int i = 0; i < array.length - 1; i++) {
            // 2.1 交换之前的无序区间 [0, n - i)
            swap(array, 0, array.length - i - 1);
            // 交换之后的无序区间 [0, n - i - 1),元素个数 n - i - 1 个
            // 2.1 对堆的 [0] 进行向下调整,堆里的元素个数就是无序区间的元素个数
            shiftDown(array, array.length - i - 1, 0);
        }
    }
    
    //进行向下调整
    private static void shiftDown(long[] array, int size, int index) {
        while (2 * index + 1 < size) {
            int maxIdx = 2 * index + 1;
            int right = maxIdx + 1;
            if (right < size && array[right] > array[maxIdx]) {
                maxIdx = right;
            }

            if (array[index] >= array[maxIdx]) {
                return;
            }

            swap(array, index, maxIdx);

            index = maxIdx;
        }
    }
    
    //创建大根堆
    private static void createBigHeap(long[] array) {
        // 从最后一个元素的双亲开始
        for (int i = (array.length - 2) / 2; i >= 0; i--) {
            shiftDown(array, array.length, i);
        }
    }


  • 快速排序(quickSort)

        原理:将一个序列中的第一个元素作为参照数,利用两个游标(左和右),左游标向右走,找大于基准值得;右游标向左走,找小于基准值的值,然后将序列中所有大于等于参照数的元素放在它后面,把所有小于参照数的元素放在它前面。通过一次快速排序,可将区间分为两部分,前面区间的元素小于后面区间的元素。此时再对两个区间分别进行快速排序,直到整个区间变得有序。

        步骤:1.确定一个基准值(通常以序列的第一个元素为基准值)

                   2.利用两个游标进行比较,将小于基准值的元素放前面,将大于等于基准值的元素放后         面;一次快排结束以后,基准值处于中间位置,也就是整个序列的分界点

                   3.此时利用递归的思想,对两个区间进行快速排序

        注意:这个基准值的选取和两个游标的位置不是固定的,是以区间划分的,每次快速排序需要在对应的区间选取相应的基准值和游标。

        下面举例为大家讲解:

        给定一个序列{4,2,6,9,1,3,5,8}

        首先确定基准值为第一个元素  4,同时准备两个有游标  i  和   j,分别指向第一个元素和最后一个元素

        然后先让j向左走,寻找比基准值  4 小的元素,所以j会在指向3的时候停下来,然后让i往右走,寻找大于 4 的元素,所以i会在指向6的时候停下来,然后交换i 和 j指向的元素,即交换 6 和 3

         这时继续上述操作,让j先走,会在1处停下,然后i走,会在9处停下,交换两个元素

 

         此时继续,但是 j 往左走一步就会与i重合,此时将j所指的1和基准值4进行交换,形成一个新序列 {1,2,3,4,9,6,5,8},这时就会发现以4位界,前面的都是小于4的,后面的都是大于4的。然后继续之前的操作,但此时是在两个区间分别进行操作,所以取的基准值是不相同的。利用递归的思想,在经历几次操作之后就会把一个无须的序列变成一个升序序列——{1,2,3,4,5,6,8,9}。

        代码如下:

public static int[] quickSort(int[] arr, int low, int high) {
        if (low > high) {
            return null;
        }
        int i = low;
        int j = high;
        int temp = arr[low];    //temp就是参照数
        while (i < j) {

            while (temp <= arr[j] && i < j) {    //先走指针 j ,从后往前移动
                j--;
            }

            while (temp >= arr[i] && i < j) {    //再走指针 i ,从前往后移动
                i++;
            }

            if (i < j) {    //交换两个符合要求的元素
                int t = arr[j];
                arr[j] = arr[i];
                arr[i] = t;
            }
        }

        //将指针重合位置的元素与参照数进行交换
        arr[low] = arr[i];
        arr[i] = temp;

        quickSort(arr, low, i - 1);    //递归左边的序列
        quickSort(arr, i + 1, high);    //递归右边的序列
        return arr;
    }

  • 归并排序(mergeSort)

        原理:归并排序的核心思想是分治。将整个区间划分成与若干个小区间,对每个区间进行排序,然后进行合并(合并区间的原理是合并有序数组)。若一个区间有6个元素,那么最终会划分成6个区间,每个区间都是一个单独的元素,而每个区间也都成了有序区间。之后进行合并,合并后成为了3个区间,每两个元素为一个区间,对3个区间进行排序,形成了三个有序的区间。重复上述操作,直到合并成为一个区间,最后在进行一次排序,即可将整个序列变为一个有序的序列。

        下面举一个例子进行详解:

        给出一个序列{4,2,6,9,1,3,5,8},下面为整个过程

        代码如下:  

public static void mergeSort(long[] array){
        mergeSortRange(array,0,array.length);
    }
    
    //处理待合并的区间
    public static void mergeSortRange(long[] array,int from,int to){
        int size=to-from;
        if(size==1){
            return;
        }

        int mid=from+(size/2);

        mergeSortRange(array,from,mid);
        mergeSortRange(array,mid,to);

        merge(array,from,mid,to);
    }

    //合并有序数组
    public static void merge(long[] array,int from,int mid,int to){
        int size=to-from;
        long[] other=new long[size];

        int left=from;  //左边小区间的下标
        int right=mid;  //右边小区间的下标
        int dest=0;     //临时空间的下标

        while(left<mid && right<to){
            if(array[left]<array[right]){
                other[dest++]=array[left++];
            }else{
                other[dest++]=array[right++];
            }
        }

        while(left<mid){
            other[dest++]=array[left++];
        }
        while (right<to){
            other[dest++]=array[right++];
        }

        for(int i=0;i<size;i++){
            array[from+i]=other[i];
        }
    }

  • 20
    点赞
  • 75
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值