常用的排序算法

2018-10-07  本文已影响32人  wenju_song

排序算法几乎是每个程序开发者都要必备的技能,从大学期间C语言学习的冒泡和选择排序,到后来数据结构课程学习的各种其他排序。这篇文章再次梳理一下这些经典的排序算法。

各种排序算法的时间复杂度


如上图,这里列出了各种排序的时间和空间复杂度。记得在校招期间,一个面试官问我们一群学生,排序算法中那个最快。很多的同学回答了快速排序,然后被直接的pass了,当时我回答了一个堆排序,拿到了后面继续面试的资格。

可以看到快速排序和堆排序这里平均的情况是一样的都是o(nlog2 n),但是在当序列已经接近排好的情况时快速排序的时间复杂度是o(n^2),堆排序没有变。并且堆排序的空间复杂度比快速排序要小。
这里的稳定指的是相同元素,会不会占用内存。
什么时候用什么排序呢?

交换排序

交换排序是每次比较,如果元素和序列不同就要交换。故为交换排序。

冒泡排序

算法思想:比较相邻的元素。如果第一个比第二个大,就交换他们两个。

  private static void sortByBubble(int[] num) {
        //第一层循环从数组的最后往前遍历
        for (int i = num.length - 1; i > 0; --i) {
            //这里循环的上界是 i - 1,在这里体现出 “将每一趟排序选出来的最大的数从sorted中移除”
            for (int j = 0; j < i; j++) {
                //保证在相邻的两个数中比较选出最大的并且进行交换(冒泡过程)
                if (num[j] > num[j + 1]) {
                    int temp = num[j];
                    num[j] = num[j + 1];
                    num[j + 1] = temp;
                }

            }
            for (int k = 0; k < num.length; k++) {
                System.out.print(num[k] + " ");
            }
            System.out.print("\n");
        }
    }

记忆技巧:冒泡是将最大元素冒出来,第一个最大冒出来之后,下一次再遍历的时候比较的元素会少一,这里双层for循环。

快速排序

算法思想:本质来说,快速排序是冒泡排序的升级版,也是一种交换排序
由于快速排序是一种分治算法,我们可以用分治思想将快排分为三个步骤:

  1. 分:设定一个分割值,并根据它将数据分为两部分
  2. 治:分别在两部分用递归的方式,继续使用快速排序法
  3. 合:对分割的部分排序直到完成
图解:

代码实现

 private static int getMiddle(int[] list, int low, int high) {
        int tmp = list[low];//数组的第一个作为中轴
        while (low < high) {
            while (low < high && list[high] >= tmp) {
                high--;
            }
            list[low] = list[high];   //比中轴小的记录移到低端
            while (low < high && list[low] <= tmp) {
                low++;
            }
            list[high] = list[low];   //比中轴大的记录移到高端
        }
        list[low] = tmp;              //中轴记录到尾
        return low;                   //返回中轴的位置
    }
    private static void quickSort(int[] list, int low, int high){
        if (low < high) {
            int middle = getMiddle(list, low, high);       //将list数组进行一分为二
            quickSort(list, low, middle - 1);        //对低字表进行递归排序
            quickSort(list, middle + 1, high);       //对高字表进行递归排序
        }
    }

记忆技巧:

选择排序

选择排序是在一轮比较之后,选择最值,然后和排序开始位置交换。

直接选择排序

基本思想:在要排序的一组数中,选出最小的一个数与第一个位置的数交换;然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环到倒数第二个数和最后一个数比较为止。

 private static void selectSort(int[] nums) {
        int k, temp; //定义k为最小值的索引,temp是用于交换的临时变量
        for (int i = 0; i < nums.length; i++) {
            k = i; //每次假设第一个最小
            for (int j = i + 1; j < nums.length; j++) {
                if (nums[j] < nums[k]) {
                    k = j;//k是动态记录最小值,这里只记录最小的下标。
                }
            }
            temp = nums[i];
            nums[i] = nums[k];
            nums[k] = temp;
        }
    }

记忆技巧: 双层for循环,第二层只记录最值位置,在第一层内部进行交换。

堆排序

什么是堆?

堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。如下图:
对堆中的结点按层进行编号,将这种逻辑结构映射到数组中就是下面这个样子

大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]
堆排序基本思想及步骤

 private static void heapSort(int[] arr) {
        //1.构建大顶堆
        for (int i = arr.length / 2; i >= 0; i--) {
            //从第一个非叶子结点从下至上,从右至左调整结构
            adjustHeap(arr, i, arr.length);
        }
        for (int j = arr.length - 1; j > 0; j--) {
            swap(arr, 0, j);
            adjustHeap(arr, 0, j);
        }

    }

    private static void adjustHeap(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 nums
     */
    public static void insertSort(int nums[]){
        //插入排序是把原有序列的元素插入到已经排好的序列中。需要双层for循环。
        //第一层第一次for循环从第二个元素开始往第一个元素插入。一直到最后一个num
        //第二层要比较从0开始到i-1的所有元素
        for (int i = 1 ;i< nums.length;i++){
            for (int j = 0;j < i;j++ ){
                if (nums[i]<nums[j]){
                    //如果不满足条件,进行交换
                    int temp = nums[i];
                    nums[i] = nums[j];
                    nums[j] = temp;
                }
            }
        }
    }

记忆技巧:

希尔排序
  private static void shellSort(int[] nums) {
        for (int increment = nums.length / 2; increment > 0; increment /= 2) {
            System.out.println("increment:" + increment);
            for (int i = increment; i < nums.length; i++) {
                System.out.println(" i:" + i);
                for (int j = i; j >= increment; j -= increment) {
                    System.out.print(" j:" + j + ",j-increment:" + (j - increment));
                    if (nums[i] < nums[j - increment]) {
                        int temp = nums[i];
                        nums[i] = nums[j - increment];
                        nums[j - increment] = temp;
                    } else {
                        break;
                    }
                }
                System.out.println();
            }
        }
    }

以上就是我对算法排序的理解。

上一篇 下一篇

猜你喜欢

热点阅读