排序算法总结系列导读

一、前言

如果想要获取模板代码的相关内容,请直接拉到本文末尾模板代码部分查看代码!

排序算法总结系列文章是对《算法4th》一书中所列举的排序算法的总结,该系列文章中的代码、图片均源自该书以及该书的配套网站 Algorithms, 4th Edition,文章只是对知识的提炼,如果想要详细地学习各个算法,其自行阅读书籍进行学习。


二、正文

本系列文章的所有排序算法均是基于 Java 代码实现的,它们的分布如下:

  • 排序算法总结(一):选择排序、插入排序和希尔排序;
  • 排序算法总结(二):归并排序和快速排序;
  • 排序算法总结(三):优先队列和堆排序。

所有的文章介绍思路基本上都是按照思想、代码实现、复杂度分析和特点/使用场景进行铺展开的。学习过程中应当注意各个算法之间的区别以及它们各自的使用场景,例如快速排序虽然在大部分情况下是排序速度最快的算法,但是在处理短小的数组时插入排序却可能会比快速排序快上不少;再例如归并排序虽然在排序速度和空间复杂度上均不如快速排序,但是因其具有稳定性的关系,Java 的 Arrays.sort() 方法引用数据类型参数的实现采用的是归并排序而并非快速排序,这些都是值得我们去注意的点。下表是各个算法的复杂度比较:
排序算法总结系列导读_第1张图片
除此之外我们可能还会遇见 TopM 问题,即在   N \ N  N 个数中找出最大的   M \ M  M 个数(   N ≫ M \ N \gg M  NM)或者找出一个大数组的中位数(   k \ k  k 值问题)。这类问题都属于处理数据很大而目标数据很小的场景,如果采用常规的排序算法的话,显然会花费大量的时间进行排序,而最终需要的数据又没有那么多,颇有杀鸡用牛刀的感觉。因此对于这类问题我们往往会采用部分排序的方式去解决,例如 TopM 问题我们会采用一个大小为   M + 1 \ M+1  M+1 的优先队列去解决,而   k \ k  k 值问题采用快速排序中切分的思想同样能很快地求出答案。

TopM 问题答案:
public class TopM {

    public static void main(String[] args) throws FileNotFoundException {
        int M =10;   // 打印输入流中最大的M个元素
        // MinPQ的详细实现见排序算法总结(三)或者直接用 PriorityQueue 替换即可
        MinPQ<String> pq = new MinPQ<>(M+1);
        Scanner scanner = new Scanner(new File("src/com/marck/sort/tale.txt"));
        while (scanner.hasNext()){
            String word = scanner.next();
            pq.insert(word);
            if (pq.size() > M){
                pq.delMin();  // 如果优先队列中存在M+1个元素就删除其中最小的元素
            }
        }  // while执行完毕之后最大的M个元素就都留在优先队列中了

        while (!pq.isEmpty()){
            System.out.print(pq.delMin() + " ");
        }
    }

}
k 值问题答案:

  k \ k  k 值问题我们直接封装成一个 select(int k) 方法,表示返回第   k \ k  k 大的数:

public class NoK {
    
    public Comparable select(Comparable[] a, int k){
        int lo = 0, hi = a.length-1;
        while (lo < hi){
            int j = partition(a, lo, hi);
            if      (j == k)  return a[k];
            else if (j > k)   hi = j-1;
            else              lo = j+1;
        }
        return a[k];
    }
    
    // 切分方法,详细介绍见排序算法总结(二)快速排序部分
    private static int partition(Comparable[] a, int lo, int hi){
        int i = lo, j = hi+1;
        Comparable v = a[lo];
        while (true){
            while (less(a[++i], v)) if (i == hi) break;
            while (less(v, a[--j])) if (j == lo) break;
            if (i >= j){
                break;
            }
            exch(a, i, j);
        }
        exch(a, lo, j);
        return j;
    }

    /***************************************************************************
     *  if v < w return true, otherwise return false.
     ***************************************************************************/
    private static boolean less(@NotNull Comparable v, @NotNull Comparable w){
        return v.compareTo(w) < 0;
    }

    /***************************************************************************
     *  exchange a[i] and a[j]
     ***************************************************************************/
    private static void exch(Comparable[] a, int i, int j){
        Comparable temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
}

再谈一谈关于 Java 中的排序算法实现。虽然《算法4th》一书竭尽全力地将算法中的具体实现和 Java 的语言特性隔离开,但有些部分还是必须依赖于 Java 语言当中的特点。对于排序算法来说首先第一大问题就是如何确定两个对象之间的大小关系,Java 给出的答案是使对象变得可比较(实现Comparable接口)或者使用比较器(Comparator),关于 Comparable 和 Comparator 可以查看这篇文章 Java 基础之 Comparable & Comparator 进行学习。对于其他语言来说,只要解决了对象之间的比较问题,那么本系列中的代码都是可以很轻易的使用的。

在我们的排序算法实现中,我们往往需要给出两种 sort() 方法,一种提供给实现了 Comparable 接口的对象,另一种则提供给未实现 Comparable 接口的对象,这种实现我们需要一个比较器来辅助排序,以我们的插入排序为例,它所提供的两个 sort() 方法如下所示:

public class Insertion {

    public static void sort(Comparable[] a){
        int n = a.length;
        for (int i = 1; i < n ; i++){
            for (int j = i; j > 0 && less(a[j], a[j-1]); j--){
                exch(a, j, j-1);
            }
        }
        assert isSorted(a);
    }

    public static void sort(Object[] a, Comparator comparator){
        int n = a.length;
        for (int i = 1; i < n; i++){
            for (int j = i; j > 0 && less(a[j], a[j-1], comparator); j--){
                exch(a, j, j-1);
            }
        }
        assert isSorted(a, comparator);
    }

	// less()、exch()和isSorted()方法见本文末尾
}

可以看到对于 Object 类型的对象,我们必须提供一个比较器来辅助进行比较。而在文章中我们往往只提供了 Comparable 对象的实现,Object 对象的方法实现与其极其相似,读者可以自行完成。

最后是关于模板代码的,模板代码中主要提供了排序所需的辅助方法,排序中最为主要的操作就是比较和交换了,除此之外为了便于验证我们的代码我们还添加了检测数组是否是按升序排列的 isSorted() 方法以及打印数组内容的方法 show()。由于我们的 sort() 方法可以对 Comparable 和 Object 两种对象进行排序,所以这些方法我们也往往需要提供两套,如下所示:

模板代码

public class SortExample {

    public static void sort(Comparable[] a){
        // 具体的算法实现
    }

	public static void sort(Object[] a, Comparator comparator){
		// 具体的算法实现
	}

    /***************************************************************************
     *  if v < w return true, otherwise return false.
     ***************************************************************************/
    private static boolean less(Comparable v, Comparable w){
        return v.compareTo(w) < 0;
    }
    
	private static boolean less(Object v, Object w, Comparator comparator){
        return comparator.compare(v, w) < 0;
    }

    /***************************************************************************
     *  exchange a[i] and a[j]
     ***************************************************************************/
    private static void exch(Comparable[] a, int i, int j){
        Comparable temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
    
    private static void exch(Object[] a, int i, int j){
        Object temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }

    /***************************************************************************
     *  Check if array is sorted - useful for debugging.
     ***************************************************************************/
    private static boolean isSorted(Comparable[] a){
        int n = a.length;
        for (int i = 1; i < n; i++){
            if (less(a[i], a[i-1])){
                return false;
            }
        }
        return true;
    }
    
    private static boolean isSorted(Object[] a, Comparator comparator){
        int n = a.length;
        for (int i = 1; i < n; i++){
            if (less(a[i+1], a[i], comparator)){
                return false;
            }
        }
        return true;
    }
    
    /***************************************************************************
     *  print array to standard output
     ***************************************************************************/
    public static void show(Comparable[] a){
        for (int i = 0; i < a.length; i++){
            System.out.print(a[i] + " ");
        }
        System.out.println();
    }
    
    public static void show(Object[] a){
        for (int i = 0; i < a.length; i++){
            System.out.print(a[i] + " ");
        }
        System.out.println();
    }

    /**
     * Reads in a sequence of strings from standard input; selection sorts them; 
     * and prints them to standard output in ascending order. 
     *
     * @param args the command-line arguments
     */
    public static void main(String[] args) throws FileNotFoundException {
        Scanner scanner = new Scanner(System.in);
        String sentences = scanner.nextLine();
        String[] words = sentences.split(" ");
        sort(words);
        show(words);
    }
}

最后说一下关于面试中出现的排序算法问题。数学家们已经推算出以下结论:

命题。没有任何基于比较的算法能够保证用少于   l g ( N ! ) ∼ N l g N \ lg(N!) \sim NlgN  lg(N!)NlgN 次比较将长度为   N \ N  N 的数组排序。

所以当我们在面试中遇到一些超过这个复杂度排序问题(例如复杂度为   N \ N  N),采用常规的方法肯定是行不通的,此时我们应当抓住面试官所给出数据的特点,根据数据的特点给出有效的解决方案,而不是一味地去比较这些经典的通用算法哪个可以实现该需求。


该文章写的过程比较仓促,如果有写的不好或者不懂的地方可以给我留言或者私信,笔者将会尽量快地回复,今后还有需要补充的地方将会进行更新补充…

希望这篇文章对您有所帮助~

你可能感兴趣的:(算法和数据结构,模板代码)