美文网首页
5种常用、高效的排序算法

5种常用、高效的排序算法

作者: MikeShine | 来源:发表于2019-08-14 11:28 被阅读0次

之前有一篇文章讲过快排。其核心思想就是:用3个指针,两个代表head & tail ,一个代表 key,在一趟排序中,将所有比 key 大的放在一边,小的放在另外一边,形成一个相对有序的数列,之后用递归,将前后两个序列继续快排,直到结束。
今天我们看一下其他的5种排序算法:选择排序、插入排序、归并排序、希尔排序、堆排序。

争取一个星期写一个,消化一个。


1. 选择排序

基础思想

这个思想非常简单。
就是每遍历一次,选择出最小的一个元素,与最前面一个位置元素做交换
然后进行下一次遍历,从第二个位置开始。
直到遍历完。

复杂度分析

第一轮比较 (n-1) 次,第2轮 (n-2) 次。。。总共的 (n-2)+(n-1)+...+1 = {{n(n-1)}\over2}所以时间复杂度是 O(n^2)

代码
package day_53;

//  选择排序。核心思想为每一轮遍历进行选出一个元素。然后遍历剩下的

public class SelectionSort {

    public void selectionSrot(int nums[]){
       for(int i=0;i<nums.length-1;i++){ //这里就是说做 n-1次选择
           int index_tmp = i;
           int tmp1 = nums[i];
           for(int j=i;j<nums.length;j++){
                // 在一轮遍历中要找出最小的元素
                if(nums[j]<tmp1){
                    tmp1 = nums[j];
                    index_tmp = j;
                }
            }
           // 完成一轮遍历之后做交换
            int tmp2 = nums[index_tmp];
            nums[index_tmp] = nums[i];
            nums[i] = tmp2;
       }
    }

    public static void main(String args[]){
        int heights[] = {181,169,187,172,163};
        SelectionSort s = new SelectionSort();
        s.selectionSrot(heights);
        for(int height : heights ){
            System.out.print(height);
        }
    }
}

2. 插入排序

基础思想

基础思想很简单:即将元素插入到有序数组中即可。
插入规则:将 candidate element 依次和有序的数组进行比较,确定插入位置。有序数组最开始是第一个ele。

复杂度分析

这里的复杂度分析稍微麻烦一点。
这里有两步操作,比较和赋值。看一下别人的推导。


复杂度分析

所以,结论是:O(n^2)

代码
package day_54;

// 插入排序。
// 思想也很简单,每次将一个元素插入到有序数组中。有序数组从1个元素开始
// 插入规则是,依次比较,找到比他大的,插到前面,否则放在最后。

public class InsertionSort {
    public void insertionSort(int[] nums){
        for (int i=1;i<nums.length;i++){
            for (int j=0;j<i;j++){
                if(nums[i]<nums[j]){
                    // 插入操作
                    int tmp = nums[i];
                    int k = i;
                    while (k>j)
                        nums[k] = nums[--k];
                    nums[j] = tmp;
                    // 插入完成后跳出循环
                    break;
                }
            }
        }
    }

    public static void main(String args[]){
        int a[] = {99,91,3,4,2,8,6,5};
        InsertionSort i = new InsertionSort();
        i.insertionSort(a);
        for(int ele : a){
            System.out.println(ele);
        }
    }
}

上面是我自己写的版本。之后在写希尔排序的时候,使用了改进版的插入排序。
改进在于,从后向前,每次将元素向后移动,腾出来位置,将待插入元素插入。

 public static void insertionSort2(int[] nums){
        for(int i=1;i<nums.length;i++){
            int j;
            // 要插入的元素
            int tmp = nums[i];
            // 这个逻辑就是从后向前的,腾出来要插入的位置
            for(j=i;j>0 && nums[j-1]>tmp;j--){
                nums[j] = nums[j-1];
            }
            nums[j] = tmp;
        }
    }

3. 归并排序

思想

MergeSort的核心思想是分治。但是实际上比的思想并不是很简单,没有上面的两个直观。所以今天看了别人的步骤图,也没有直接写出来代码。还是看了别人代码的结构才慢慢写出来的。可以参考这个老哥的笔记。
这里我自己总结一下,所谓的归并就是说把 有序的数组再归并到一个数组。这里当然要用一个 tmp[] 数组增加一点空间开销来换取时间开销。
问题的关键是,这里肯定是递归的。但是怎样递归呢?首先是要 divide,然后要 merge,所做操作都是针对同一个数组的,我们只需要指定出操作数组的范围即可。
先来看 merge() 方法,这里用一个例子,想要将两个有序数组(连续的,不是两个独立的)合并成一个,需要 (left,mid,right) 三个参数,其中 mid 标记的是两者的分界。
然后看 divide() 方法,divide 方法内才是递归发生的地方。其中递归终止条件是 left==right,只有一个元素了就不再 divide 了。同时调用下一层 divide,最终到 divide结束之后,调用 merge。

层层向下,尽头merge,最终向上!

复杂度分析

divide & conqure 时间复杂度就是 O(nlogn),空间复杂度是 O(n)

代码
package day_55;

// 归并排序
// 核心思想是分治
// 先divide 到头才可以 merge,所以是divide中递归

public class MergeSort {
    public static void mergeSort(int nums[]){
        divide(nums,0,nums.length-1);
    }

    public static void divide(int nums[], int left, int right){
        if(left==right)
            return;
        int mid = (left + right)/2;
        divide(nums,left,mid);
        divide(nums,mid+1,right);
        merge(nums,left,mid,right);
    }

    public static void merge(int nums[],int left,int mid, int right){
        // 用于暂存的数组
        int tmp[] = new int[right-left+1];
        int index = 0;
        int r = mid+1;
        int l = left;
        while(l<=mid && r<=right) {
            if (nums[l] <= nums[r])
            {
                tmp[index++] = nums[l++];
                continue;
            }
            if (nums[r] < nums[l])
            {
                tmp[index++] = nums[r++];
                continue;
            }
        }
        //
        if(l>mid){
            for(int k=r;k<=right;k++){
                tmp[index++] = nums[k];
            }
        }
        if(r>right){
            for(int k=l;k<=mid;k++){
                tmp[index++] = nums[k];
            }
        }
        // 暂存数组写回去
        for(int j=0;j<tmp.length;j++){
            nums[left++] = tmp[j];
        }


    }

    public static void  main(String args[]){
        MergeSort m = new MergeSort();
        int a[] = {21,31,32,2,3,4,98,45,10};
        m.mergeSort(a);
        for(int ele : a){
            System.out.println(ele);
        }
    }
}


4. 希尔排序

思想

希尔排序可以说是插入排序的升级版。由于插入排序在小数据量和基本有序数据的基础上效率更高。所以出现了希尔排序。
希尔排序的核心思想是将待排序数组进行逻辑分组,每个分组内部使用插入排序。
通过一个 gap来控制分组,gap折半直到为1,此时排序完成。

复杂度

这里是增量时间复杂度(所谓增量就是每一次通过折半来产生新的增量)。
只需要记住结论:O(n^{1.3}-n^2)

代码

跟上面思路里面说的一样,其实分成两步,用 gap 控制分组,然后每个分组内用插入排序。

package day_57;

// 希尔排序是插入排序的高级版本。因为插入排序对于小规模数据或基本有序数据十分高效。
// 在此基础上,有了希尔排序。

public class ShellSort {
    private void shellSort(int[] nums){
        int n = nums.length;
        for(int gap=n/2;gap>0;gap/=2){
            for(int i=0;i<gap;i++){
                // 对这一个逻辑分组进行插入排序
                insertSort(nums,gap,i);
            }
        }
    }

    private void insertSort(int nums[],int gap, int i){
        int k;
        for(int j=i+gap;j<nums.length;j+=gap){
            int tmp = nums[j];
            for(k=j-gap;k>=0 && tmp<nums[k];k-=gap){
                nums[k+gap] = nums[k];
            }
            nums[k+gap] = tmp;
        }
    }

    public static void main(String args[]){
        ShellSort s = new ShellSort();
        int a[] = {2,3,5,32,13,1,44,22};
        s.shellSort(a);
        for (int ele : a){
            System.out.println(ele);
        }
    }
}


5. 堆排序

思想:

堆排序的思想很简单,就是利用大根堆的特性,将数组和大根堆结合起来,通过不断建立、更新大根堆的过程来完成排序。

具体的过程参考别人的动画:https://www.jianshu.com/p/0d383d294a80

总结下来,就两步:

  1. 建立大根堆:这一部分是最耗时的一步,由于为了要保证“父>子”的特性,所以需要从后向前,一步步建立。

  2. 维护大根堆:完成大根堆建立之后,最大的元素就会被放在根节点上(即nums[0]),这时交换 nums[0] 和 nums[len-1]。交换之后大根堆并不稳定,需要重新维护大根堆,但是由于这时,大根堆基本有序,所以并不需要再从后向前维护了,只需要从前向后维护即可,即选定 root = 0. 长度递减。

复杂度分析:

代码
package day_60;

// 这是代码比较简洁的 堆排序
// 前面那个有点复杂,不过想法是Ok的

public class HeapSort2 {
    package Sortings;
/**
 * 堆排序。这里用了大根堆的思想。
 *     堆就是一个数组实现的二叉树
 * 总结下来就2步,建立大根堆和维护大根堆
 * 1. 建立大根堆:要保证 父亲>儿子,所以我们从后向前建立
 * 2. 维护大根堆,建堆完成后,最大的元素就在 root 上了。
 */

public class HeapSort {
    public static void heapSort(int[] arr){
        // 建堆,从第一个非叶子节点,从下至上,从右到左,调整结构
        for(int i=(arr.length-2)/2;i>=0;i--){
            shiftDown(arr,arr.length,i);
        }
        // 交换,去除,控制arr长度
        for(int j=arr.length-1;j>0;j--){
            int tmp = arr[0];
            arr[0] = arr[j];
            arr[j] = tmp;

            shiftDown(arr,j,0);
        }

    }

    /**
     * 将当前根节点及其子树调整为大根堆
     * @param arr
     * @param count
     * @param currentRoot
     */
    public static void shiftDown(int[] arr,int count,int currentRoot){
        // 如果有子节点
        while(2*currentRoot+1<count){
            int max = 2*currentRoot+1;
            // 有右子树且右子树大
            if(arr[max]<arr[max+1] && max+1<count)
                max += 1;
            // 只有向下换的才要继续比较,
            // 如果不向下换,就不用比较了,这个节点就建堆完成了
            if(arr[currentRoot]>arr[max])
                break;
            // 交换 max 和 cur
            int tmp = arr[max];
            arr[max] = arr[currentRoot];
            arr[currentRoot] = tmp;
            // 向下递归
            currentRoot = max;
        }
    }

    public static void main(String args[]){
        int a[] = {5,2,7,3,6,1,4};
        HeapSort h2 = new HeapSort();
        h2.heapSort(a);
        for(int ele:a){
            System.out.println(ele);
        }
    }
}

}

相关文章

  • java排序方法资料

    java排序,效率高的是哪种排序方法 JAVA快速排序(高效) java中常用的几种排序算法 相关代码: /* *...

  • 算法04-棋牌游戏常用排序算法

    算法04-棋牌游戏常用排序算法 一、介绍 棋牌游戏常用排序算法包括:链式基数排序、插入排序、希尔排序。 二、链式基...

  • 排序算法--快速排序

    快速排序是最经典,最常用的高效排序算法之一。快排和归并排序算法一样,采用的是分治的思想。具体步骤如下: 分:从未排...

  • python 排序算法

    文章概述 介绍各大常用经典的排序算法和效率,以及python实现常用算法(冒泡排序,选择排序,快速排序,插入排序)...

  • 常用的排序算法

    常用的排序算法 常用的排序算法插入排序折半插入排序shell 排序冒泡排序选择排序快速排序基数排序归并排序堆排序K...

  • 全面介绍9种常用的排序算法

    本篇给大家介绍几种软件工程中常用的排序算法 所有排序算法的核心的代码都在《常用排序算法核心代码》[https://...

  • 常用算法

    常用排序算法

  • 常见排序算法

    常用排序算法

  • Java语言——数组排序算法

    数组有很多常用的算法,包括冒泡排序、直接选择排序和反转排序。 一、冒泡排序 冒泡排序是最常用的数组排序算法之一,它...

  • 算法学习(1)-排序算法

    八大排序算法九大排序算法再总结[经典排序算法][集锦][直观学习排序算法] 视觉直观感受若干常用排序算法 快速排序...

网友评论

      本文标题:5种常用、高效的排序算法

      本文链接:https://www.haomeiwen.com/subject/xgowjctx.html