《算法》2.5应用

1将各种数据排序

在一个有序的数组中查找一个元素比在一个无序的数组中查找要简单得多。

也就是说:

  • 排序是为了查找而服务的。

1.1交易事务

排序算法的一种典型应用就是商业数据的处理。

一家成功的商业公司需要能够处理数百万的交易数据。

1.2指针排序

我们全书基于java的算法都被称为“指针排序”。

因为我们只处理了元素的引用,而并没有移动数据本身。

  • 除了原始数据类型之外,我们的操作总是数据的引用,而不是数据的本身。

1.3不可变的键

如果在排序之后,用例还能够修改元素的键值,那么数据就很可能不再是有序的了。

因此:

  • java中,可以使用不可变的数据类型来作为键来避免这个问题。

例如:String,Integer,Double,File

1.4廉价的交换

对于任意大小的元素,使用引用使得在一般情况下交换的成本和比较的成本几乎相同(代价只是需要额外的空间来存储这些引用)。

1.5多种排序方法

在许多应用中,我们希望能够根据情况将一组对象按照不同的方式排序。

因此:

  • Comparator接口允许我们为任意数据类型定义任意多种排序方法。
  • Comparator接口来代替Comparable接口能够更好地将数据类型的定义和两个该类型的对象的比较的定义区分开来。

1.6多键数组

一般在应用程序中,一个元素的多种属性都可以被用作排序的键。

因此:

Comparator接口正好合适,我们可以定义多种比较器。

/**
 * 使用了Comparator的插入排序
 * @Author: AZhu
 * @Date: 2021/2/24 16:18
 */
public class InsertionByCpt {

    /**
     * 使用了Comparator的插入排序
     * @param a
     * @param c
     */
    public static void sort(Object[] a, Comparator c) {
        int N = a.length;
        for (int i = 0; i < N; i++) {
            for (int j = i; j > 0 && less(c, a[j], a[j - 1]); j--) {
                exch(a, j, j - 1);
            }
        }
    }

    /**
     * 使用比较器c来比较对象v和w的大小
     * @param c
     * @param v
     * @param w
     * @return
     */
    private static boolean less(Comparator c, Object v, Object w) {
        return c.compare(v, w) < 0;
    }

    /**
     * 在数组a中交换索引i和j中的元素
     * @param a
     * @param i
     * @param j
     */
    private static void exch(Object[] a, int i, int j) {
        Object t = a[i];
        a[i] = a[j];
        a[j] = t;
    }

}
/**
 * 记录交易信息的类
 * @Author: AZhu
 * @Date: 2021/2/24 16:38
 */
public class Transaction {
    
    private final String who;
    private final Date when;
    private final double amount;
    
    public Transaction(String who, Date when, double amount) {
        this.who = who;
        this.when = when;
        this.amount = amount;
    }

    public static class WhoOrder implements Comparator<Transaction> {
        @Override
        public int compare(Transaction v, Transaction w) {
            return v.who.compareTo(w.who);
        }
    }
    
    public static class WhenOrder implements Comparator<Transaction> {
        @Override
        public int compare(Transaction v, Transaction w) {
            return v.when.compareTo(w.when);
        }
    }

    public static class HowMuchOrder implements Comparator<Transaction> {
        @Override
        public int compare(Transaction v, Transaction w) {
            if (v.amount < w.amount) {
                return -1;
            }
            if (v.amount > w.amount) {
                return +1;
            }
            return 0;
        }
    }
    
}

1.7使用比较器实现优先队列

按照以下步骤来扩展算法2.6来实现支持比较器。

  • 导入java.util.Comparator
  • MaxPQ添加一个成员变量comparator以及一个构造器,构造器接受一个比较器作为参数并用于初始化comparator
  • less()中检查comparator属性是否为null,如果不是就用它来比较。

1.8稳定性

如果一个排序算法能够保留数组中重复元素的相对位置则可以成为是稳定的。

2我们应该使用哪种排序算法

各种排序算法的性能特点:

算法是否稳定是否为原地排序时间复杂度空间复杂度备注
选择排序N^21
插入排序N~N^21取决于输入元素的排列情况
希尔排序NlogN? N^(6/5)?1
归并排序NlogNN
快速排序NlogNlgN运行效率由概率提供保证
三向快速排序N~NlogNlgN运行效率由概率提供保证,也取决于输入元素的分布情况
堆排序NlogN1

快速排序是最快的通用排序算法。

如果稳定很重要而空间又不是问题,归并排序可能是最好的。

2.1将原始数据类型排序

如果我们只是在将一大组数进行排序的话,

跳过引用可以为我们节省存储所有引用所需的空间和通过引用来访问数字的成本,

更不用说那些调用compareTo()less()方法的开销了。

2.2java系统库的排序算法

java系统的主要排序方法位于:java.util.Arrays.sort()

实际上代表了一系列排序方法:

  • 每种原始数据类型都有一个不同的排序方法
  • 一个适用于所有实现了Comparable()接口的数据类型的排序方法
  • 一个适用于所有实现了比较器Comparator()的数据类型的排序方法

java系统的程序员选择:

  • 对原始数据类型使用三向切分的快速排序,
  • 对引用类型使用归并排序。

3问题的归约

归约指的是为了解决某个问题而发明的算法正好可以用于解决另一种问题。

排序算法也可以用于解决其他非排序问题:

3.1找出重复元素

  • 首先将数组排序
  • 然后遍历有序的数组,记录连续出现的重复元素即可。
/**
* 统计a[]中不重复元素的个数
* @param a
* @return
*/
public static int countNonredundant(Comparable[] a) {
    Quick.sort(a);
    int count = 1;
    for (int i = 1; i < a.length; i++) {
        if (a[i].compareTo(a[i - 1]) != 0) {
            count++;
        }
    }
    return count;
}

3.2排名

某个排列和标准排列(即每个元素都在正确位置上的排列)的Kandall tau距离就是其中逆序数对的数量。

3.3优先队列

例:

  • 找出输入流中M个最大的元素
  • M个输入流归并为一个有序的输出流

3.4中位数和顺序统计

  • 找出一组元素的中位数

  • 找出一组数中的第k小的元素

实现方法:

低级实现:

先对数组排序,然后数组中的k个最小的元素就是数组的前k个元素。

高级实现:

使用切分的方法,如下:

/**
* 找到一组数中的第k小元素
* @param a
* @param k
* @return
*/
public static Comparable select(Comparable[] a, int k) {
    StdRandom.shuffle(a);
    int lo = 0, hi = a.length - 1;
    while (hi > lo) {
        int j = Quick.partition(a, lo, hi);//切分
        if (j == k) {
            return a[k];//找到了
        } else if (j > k) {
            hi = j - 1;//k在左边
        } else {
            lo = j + 1;//k在右边
        } 
    }
    return a[k];
}
  • 平均来说,基于切分的选择算法的运行时间是线性级别的。

4排序应用一览

  • 商业计算
  • 信息搜索
  • 运筹学
  • 事件驱动模拟
  • 数值计算
  • 组合搜索
    • Prim算法和Dijkstra算法
    • Kruskal算法
    • 霍夫曼算法
    • 字符串处理

5答疑

Q:java的系统库中有优先队列这种数据类型吗?

A:有,请见:java.util.PriorityQueue

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值