• 首页
  • 关于
    • Pg.lost photo

      Pg.lost

    • 更多
    • LinkedIn
    • Github
    • Weibo
  • 文章
    • 列表
    • 标签
  • 项目

基本排序方法

02 Mar 2018

Reading time ~3 minutes

  • 1. 概述
    • 1.1 回调
    • 1.2 全序关系
    • 1.3 Comparable API
    • 1.4 排序的两个基本操作
    • 1.5 测试是否有序
  • 2. 选择排序
    • 2.1 基本思想
    • 2.2 算法正确性分析
    • 2.3 Java实现
    • 2.4 性能
  • 3. 插入排序
    • 3.1基本思想
    • 3.2 算法正确性分析
    • 3.3 Java实现
    • 3.3 性能分析
    • 3.3 部分有序数组
  • 4. 希尔排序
    • 4.1 基本思想
    • 4.2 h-sort
    • 4.3 h序列的选择
    • 4.4 Java实现
    • 4.5 性能分析
    • 4.6 希尔排序的启示
  • 5. 洗牌
    • 5.1 问题概述
    • 5.2 洗牌排序(Shuffle sort)
    • 5.3 Knuth shuffle
  • 6.凸包

1. 概述

1.1 回调

  • 目标:可以排序任意数据类型
  • 问题:如何针对不同的数据类型,设计一个通用的sort()方法

回调:可执行代码的引用

1.2 全序关系

元素在排序中能够按照特定顺序排列

  • 非对称性:如果 并且,那么v=w
  • 传递性:如果 并且 ,那么
  • 完全性:要么,要么 ,要么两者相等

1.3 Comparable API

实现compareTo方法,v.compareTo(w)

接口规则:

  • v < w, -1
  • v == w, 0
  • v > w, 1

1.4 排序的两个基本操作

比较:

private static boolean less(Comparable v, Comparable w) 
{  
	return v.compareTo(w) < 0;  
}

交换:

private static void exch(Comparable[] a, int i, int j)
{
   Comparable swap = a[i];
   a[i] = a[j];
   a[j] = swap;
}

1.5 测试是否有序

private static boolean isSorted(Comparable[] a)
{
   for (int i = 1; i < a.length; i++)
      if (less(a[i], a[i-1])) return false;
   return true;
}

Q:通过isSorted()测试,是否能确保数组一定有序? A:如果排序算法能保证只通过less、exch两个排序的基本方案去操作数组,那么通过isSorted()测试的数组可以保证一定是有序的。

2. 选择排序

2.1 基本思想

  • 在i次迭代中,找到无序项中最小项的索引min
  • 交换a[i]和a[min]

2.2 算法正确性分析

对于选择排序算法,有一个指针(数组索引i),从左到右对数组进行扫描

算法不变性:

  • 指针左边的项已经是升序,不会再改变
  • 指针右边的项都比左边的大

维持算法的不变性

  • 将指针右移(此时,破坏了不变性)
i++
  • 找到最小项的索引
int min = i;
for (int j = i+1; j < N; j++)
	if (less(a[j], a[min]))
   		min = j;
  • 交换
exch(a, i, min);

2.3 Java实现

public class Selection
{
	public static void sort(Comparable[] a) 
	{
		int N = a.length;
		for(int i = 0; i < N; i++)
		{
			int min = i;
			for(int j = i+1; j < N; j++)
			{
				if(less(a[j], a[min]))
					min = j;
		     		exch(a, i, min);
			}
		}
	}
   private static boolean less(Comparable v, Comparable w)
   {  /* as before */  }
   private static void exch(Comparable[] a, int i, int j)
   {  /* as before */  }
}

2.4 性能

~compare操作,N次exchange操作

算法运行时间对输入不敏感。即使是已排序数组,也需要次的比较操作。

数据移动次数最少。线性次数的交换操作。

3. 插入排序

3.1基本思想

在第i次迭代中 将a[i]与其左侧比它更大的项进行交换,直到找到合适的位置

3.2 算法正确性分析

对于插入排序算法,有一个指针(数组索引i),从左到右对数组进行扫描

  • 指针左边的项已经是升序,不会再改变
  • 指针右边的项都是未知的

保持算法的不变性

  • 将指针右移(此时,算法不变性破坏)
i++;
  • 从右到左移动a[i],与比a[i]大的每个项交换位置
for (int j = i; j > 0; j--)
	if (less(a[j], a[j-1]))
   		exch(a, j, j-1);
   	else break;	

3.3 Java实现

public class Insertion
{
   public static void sort(Comparable[] a)
   {
      int N = a.length;
      for (int i = 0; i < N; i++)
         for (int j = i; j > 0; j--)
            if (less(a[j], a[j-1]))
               exch(a, j, j-1);
            else break;
   }
   private static boolean less(Comparable v, Comparable w)
   {  /* as before */  }
   private static void exch(Comparable[] a, int i, int j)
   {  /* as before */  }
}

3.3 性能分析

  • 最好情况:数组为升序,使用插入排序算法会有N-1次比较操作,0次交换操作。优于选择排序。

  • 最坏情况:数组为降序,并且没有重复项,使用插入排序算法会有~ $次比较操作,~次交换操作。不如选择排序。

3.3 部分有序数组

  • 定义:逆序对
  • 定义:部分有序数组。逆序对数cN
  • 结论:对于部分有序数组,插入排序算法为线性时间复杂度
  • 证明:交换次数=逆序对数;比较次数=交换次数+N-1

4. 希尔排序

4.1 基本思想

基于插入排序的改进,插排每次比较之把项目向前移动1步。在对数组扫描中的每次迭代,通过h-sort算法,使得当前项向左移动大于1步。

4.2 h-sort

步长为h的插排

为什么选择插入排序?

  • 大步长(h很大),可以使得h-sort处理的子问题规模更小(子数组的排序)
  • 小步长(h小),当前数组已经基本有序,插入排序对处理部分有序数组性能很好

4.3 h序列的选择

有多种选择,还未找到最好的。最常用的是3x+1

4.4 Java实现

public class Shell {
   public static void sort(Comparable[] a) {
		int N = a.length;
		int h = 1;
		while(h < N/3) h = 3*h + 1;
		while (h >= 1) {
			for (int i = h; i < N; i++) {
				for(int j = i; j >=h; j -= h) {
					if(less(a[j], a[j-h]))
						exch(a, j, j-h);
					else break;
				}
			}
			h = h / 3;
		}
	} 
   private static boolean less(Comparable v, Comparable w) {
    /* as before */
   }
   private static void exch(Comparable[] a, int i, int j) {
    /* as before */ 
   }
}

4.5 性能分析

还未得出准确的模型。相比较于插入排序,性能有大幅度提升。

4.6 希尔排序的启示

对经典算法的简单改进可以导致性能的显著提升。一些好的算法还有待于我们去发现。

在工程中有很好的应用:

  • 对于中小型数组运行速度很快
  • 代码简单,可用于嵌入式系统
  • 硬件中排序算法的原型

5. 洗牌

5.1 问题概述

重新排列一个数组,使其均匀随机分布。

5.2 洗牌排序(Shuffle sort)

  • 对于数组中的每一项生成一个随机实数。
  • 重新排序(根据随机实数)

5.3 Knuth shuffle

  • 在第i次迭代中,在0-i间找到符合随机分布的整数r

  • 交换a[i]和a[r]

java实现

public class StdRandom {
   ...
   public static void shuffle(Object[] a) {
      int N = a.length;
      for (int i = 0; i < N; i++) {
         int r = StdRandom.uniform(i + 1);
         exch(a, i, r);
      }
	} 
}	

6.凸包



普林斯顿算法公开课 Share Tweet +1