Java核心技术卷I:基础知识(原书第8版):13.4 算法

AI权益加码!Claude Code、Cursor等20+工具免费用! 购周边限时加赠Coding Plan Lite,畅享主流AI工具!学习进阶更高效! 阅读详情

铁文整理

13.4 算法

    泛型集合接口有一个很大的优点,即算法只需要实现—次。例如,考虑一下计算集合中最大元素这样一个简单的算法。使用传统方式,程序设计人员可能会用循环实现这个算法。下面就是找出数组中最大元素的代码。

    ……

    当然,为找出数组列表中的最大元素所编写的代码会与此有微小的差别。

    ……

    链表应该怎么做呢?对于链表来说,无法实施高效的随机访问,但却可以使用迭代器。

    ……

    编写这些循环代码有些乏味,并且也很容易出错。是否存在严重错误吗?对于空容器循环能正常工作吗?对于只含有一个元素的容器又会发生什么情况呢?我们不希望每次都测试和调试这些代码,也不想实现下面这一系列的方法:

    static <T extends Comparable> T max(T[] a)

    static <T extends Comparable> T max(ArrayList<T> v)

    static <T extends Comparable> T max(LinkedList<T> v)

    这正是集合接口的用武之地。仔细考虑一下,为了高效地使用这个算法所需要的最小集接口。采用getset方法进行随机访问要比直接迭代层次高。在计算链表中最大元素的过程中已经看到,这项任务并不需要进行随机访问,直接用迭代器遍历每个元素就可以计算最大元素。因此,可以将max方法实现为能够接收任何实现了Collection接口的对象。

    public static <T extends Comparable> T max(Collection<T> c) {

        if (c.isEmpty())

            throw new NoSuchElementException();

        Iterator<T> iter = c.iterator();

        T largest = iter.next();

        while (iter.hasNext()) {

            T next = iter.next();

            if (largest.compareTo(next) < 0)

                largest = next;

        }

        return largest;

    }

    现在就可以使用一个方法计算链表、数组列表或数组中最大元素了。

    这是一个非常重要的概念。事实上,标准的C++类库已经有几十种非常有用的算法,每个算法都是在泛型集合上操作的。Java类库中的算法没有如此丰富,但是,也包含了基本的排序、二分査找等实用算法。

13.4.1 排序与混排

    计算机行业的前辈们有时会回忆起他们当年不得不使用穿孔卡片以及手工地编写排序算法的情形。当然,如今排序算法已经成为大多数编程语言标准库中的一个组成部分,Java程序设计语言也示例外。

    Collection类中的sort方法可以对实现了List接口的集合进行排序。

        List<String> staff = new LinkedList<String>();

        // fill collection ...;

        Collections.sort(staff);

    这个方法假定列表元素实现了Comparable接口。如果想采用其他方式对列表进行排序,可以将Comparator对象作为第二个参数传递给sort方法。(已经在前面的章节中介绍过比较器),下面的代码说明了对列表中各项进行排序的基本方法:

        Comparator<Item> itemComparator = new Comparator<Item>() {

            public int compare(Item a, Item b) {

                return a.partNumber - b.partNumber;

            }

        };

        Collections.sort(items, itemComparator);

    如果想按照降序对列表进行排序,可以使用一种非常方便的静态方法Collections.reverseOrder()。这个方法将返回一个比较器,比较器则返回b.compareTo(a)。例如,Collections.sort(items, Collections.reverseOrder());这个方法将根据元素类型的compareTo方法给定排序顺序,按照逆序对列表staff进行排序。

    同样,Collections.sort(items, Collections.reverseOrder(itemComparator));将逆置itemComparator的次序。

    人们可能会对sort方法所采用的排序手段感到好奇。通常,在翻阅有关算法书籍中的排序算法时,会发觉介绍的都是有关数组的排序算法,而且使用的是随机访问方式,但是,对列表进行随机访问的效率很低。实际上,可以使用归并排序对列表进行高效的排序(例如,可以参看Addison Wesley出版社1998年出版的Robert Sedgewick编写的《Algorithms in C++》第366~369页)。然而,Java程序设计语言并不是这样实现的。它直接将所有元素转入一个数组,并使用一种归并排序的变体对数组进行排序,然后,再将排序后的序列复制回列表。

    集合类库中使用的归并排序算法比快速排序要慢一些,快速排序是通用排序算法的传统选择。但是,归并排序有一个主要的优点:稳定,即不需要交换相同的元素。为什么要关注相同元素的顺序呢?下面是一种常见的情况。假设有一个已经按照姓名排列的员工列表。现在,要按照工资再进行排序。如果两个雇员的工资相等发生什么情况呢?如果采用稳定的排序算法,将会保留按名字排列的顺序。换句话说,排序的结果将会产生这样一个列表,首先按照工资排序,工资相同者再按照姓名排序。

    因为集合不需要实现所有的“可选”方法,因此,所有接受集合参数的方法必须描述什么时候可以安全地将集合传递给算法。例如,显然不能将unmodifiableList列表传递给排序算法。可以传递什么类型的列表呢?根据文档说明,列表必须是可修改的,但不必是可以改变大小的。

    下面是有关的术语定义:

  • 如果列表支持set方法,则是可修改的。

  • 如果列表支持add和remove方法,则是可改变大小的。

    Collections类有一个算法shuffle,其功能与排序刚好相反,即随机地混排列表中元素的顺序。例如:

        ArrayList<Card> cards = ...;

        Collections.shuffle(cards);

    如果提供的列表没有实现RandomAccess接口,shuffle方法将元素复制到数组中,然后打乱数组元素的顺序,最后再将打乱顺序后的元素复制回列表。

    13-6的程序用1~49之间的49Integer对象填充数组。然后,随机地打乱列表,并从打乱后的列表中选前6个值。最后再将选择的数值进行排序和打印。

13-6 ShuffleTest.java

import java.util.*;

 

/**

 * This program demonstrates the random shuffle and sort algorithms.

 *

 * @version 1.10 2004-08-02

 * @author Cay Horstmann

 */

public class ShuffleTest {

    public static void main(String[] args) {

        List<Integer> numbers = new ArrayList<Integer>();

        for (int i = 1; i <= 49; i++)

            numbers.add(i);

        Collections.shuffle(numbers);

        List<Integer> winningCombination = numbers.subList(0, 6);

        Collections.sort(winningCombination);

        System.out.println(winningCombination);

    }

}

APIjava.util.Collections 1.2

  • static <T extends Comparable<? super T>> void sort(List<T> elements)

  • static <T> void sort(List<T> elements, Comparator<? super T> c):使用稳定的排序算法,对列表中的元素进行徘序,这个算法的时间复杂度是O(nlogn),其中n为列表的长度。

  • static void shuffle(List<?> elements)

  • static void shuffle(List<?> elements, Random r):随机地打乱列表中的元素。这个算法的时间复杂度是O(na(n)),n是列表的长度,a(n)是访问元素的平均时间。

  • static <T> Comparator<T> reverseOrder():返回一个比较器,它用与Comparable接口的compareTo方法规定的顺序的逆序对元素进行排序。

  • static <T> Comparator<T> revevseOrder(Comparator<T> comp):返回一个比较器,它用与comp给定的顺序的逆序对元素进行排序。

13.4.2 二分査找

    要想在数组中查找一个对象,通常要依次访问数组中的每个元素,直到找到匹配的元素为止。然而,如果数组是有序的,就可以直接査看位于数组中间的元素,看一看是否大于要査找的元素。如果是,用同样的方法在数组的前半部分继续查找,否则,用同样的方法在数组的后半部分继续查找。这样就可以将査找范围缩减一半。一直用这种方式査找下去。例如,如果数组中有1024个元素,可以在10次比较后定位所匹配的元素(或者可以确认在数组中不存在这样的元素),而使用线性查找,如果元素存在,平均需要512次比较,如果元素不存在,需要1024次比较才可以确认。

    Collections类的binarySearch方法实现了这个算法。注意,集合必须是排好序的,否则算法将返回错误的答案。要想査找某个元素,必须提供集合(这个集合要实现List接口,下面还要更加详细地介绍这个问题)以及要查找的元素。如果集合没有采用Comparable接口的compareTo方法进行排序,就还要提供一个比较器对象。

        i = Collections.binarySearch(c, elements);

        i = Collections.binarySearch(c, elements, comparator);

    如果binarySearch方法返回的数值大于等于0,则表示匹配对象的索引。也就是说,c.get(i)等于在这个比较顺序下的elemem。如果返回负值,则表示没有匹配的元素。但是,可以利用返回值计算应该将elemem插入到集合的哪个位置,以保持集合的有序性。插入的位置是

        insertionPoint = -i - 1;

    这并不是简单的-i,因为0值是不确定的。也就是说,下面这个操作:

        if (i < 0)

            c.add(-i - 1, element);

    将把元素插入到正确的位置上。

    只有采用随机访问,二分査找才有意义。如果必须利用迭代方式一次次地遍历链表的一半元素来找到中间位置的元素,二分査找就完全失去了优势。因此,如果为binarySearch算法提供一个链表,它将自动地变为线性査找。

    注释:在Java SE 1.3中,没有为有序集合提供专门的接口,以进行高效地随机访问,而binarySearch方法使用的是一种拙劣的策略,即检查列表参数是否扩展了AbstractSequentialList类,这个问题在Java SE 1.4中得到了解决。现在binarySearch方法检查列表参数是否实现了RandomAccess接口。如果实现了这个接口,这个方法将采用二分查找;否则,将采用线性査找。

APIjava.util.Collections 1.2

  • static <T extends Comparable<? super T>> int binarySearch(List<T> elements, T key)

  • static <T> int binarySearch(List<T> elements, T key, Comparator<? super T> c):从有序列表中搜索一个键,如果元素扩展了AbstractSequentialList类,则采用线性査找,否则将采用二分査找。这个方法的时间复杂度为O(a(n)logn),n是列表的长度,a(n)是访问一个元素的平均时间。这个方法将返回这个键在列表中的索引,如果在列表中不存在这个键将返回负值i,在这种情况下,应该将这个键插入到列表索引-i-1的位置上,以保持列表的有序性。

13.4.3 简单算法

    Collections类中包含了几个简单且很有用的算法。前面介绍的査找集合中最大元素的示例就在其中。另外还包括:将一个列表中的元素复制到另外一个列表中,用一个常量值填充容器;逆置一个列表的元素顺序。为什么会在标准库中提供这些简单算法呢?大多数程序员肯定可以很容易地采用循环实现这些算法。我们之所以喜欢这些算法是因为:它们可以让程序员阅读算法变成一件轻松的事情。当阅读由别人实现的循环时,必须要揣縻编程者的意图。而在看到诸如Collections.max这样的方法调用时,一定会立刻明白其用途。

    下面的API注释描述了C1]i5类的一些简单算法。

APIjava.util.Collections 1.2

  • static <T extends Comparable<? super T>> T min(Collection<T> elements)

  • static <T extends Comparable<? super T>> T max(Conection<T> elements)

  • static <T> min(Conection<T> elements, Comparator<? super T> c)

  • static <T> max(Collection<T> elements, Comparator<? super T> c):返回集合中最小的或最大的元素(为清楚起见,参数的边界被简化了)。

  • static <T> void copy(List<? super T> to, List<T> from):将原列表中的所有元素复制到目标列表的相应位置上。目标列表的长度至少与原列表一样。

  • static <T> void fill(List<? super T> l, T value):将列表中所有位置设置为相同的值。

  • static <T> boolean addAll(Conection<? super T> c, T... values) 5.0:将所有的值添加到集合中。如果集合改变了,则返回true。

  • static <T> boolean replaceAll(List<T> l, T oldValue, T newValue) 1.4:用newValue取代所有值为oldValue的元素。

  • static int indexOfSubList(List<?> l, List<?> s) 1.4

  • static int lastIndexOfSubList(List<?> l, List<?> s) 1.4:返回l中第一个或最后一个等于s子列表的索引。如果l中不存在等于s的子列表,则返回-1。例如,l为[s,t,a,r],s为[t,a,r],两个方法都将返回索引1。

  • static void swap(List<?> l, int i, int j) 1.4:交换给定偏移量的两个元素。

  • static void reverse(List<?> l):逆置列表中元素的顺序。例如,逆置列表[t,a,r]后将得到列表[r,a,t]。这个方法的时间复杂度为O(n),n为列表的长度。

  • static void rotate(List<?> l, int d) 1.4:旋转列表中的元素,将索引i的条目移动到位置(i+d)%l.zise。例如,将列表[r,a,r]旋转移2个位置后得到[a,r,t]。这个方法的时间复杂度为O(n),n为列表的长度。

  • static int frequency(Collection<?> c, Object o) 5.0:返回c中与对象o相同的元素个数。

  • boolean disjoint(Collection<?> c1, Collection<?> c2) 5.0:如果两个集合没有共同的元素,则返回true。

13.4.4 编写自己的算法

    如果编写自己的算法(实际上,是以集合作为参数的任何方法),应该尽可能地使用接口,而不要使用具体的实现。例如,假设想用一组菜单项填充JMenu。传统上,这种方法可能会按照下列方式实现:

    void fillMenu(JMenu menu, ArrayList<JMenuItem> items) {

        for (JMenuItem item : items)

            menu.addItem(item);

    }

    但是,这样会限制方法的调用程序,即调用程序必须在ArrayList中提供选项,如果这些选项需要放在另一个容器中,首先必须对它们重新包装,因此,最好接受一个更加通用的集合。

    什么是完成这项工作的最通用的集合接口?在这里,只需要访问所有的元素,这是Collection接口的基本功能。下面代码说明了如何重新编写fillMenu方法使之接受任意类型的集合。

    void fillMenu(JMenu menu, Collection<JMenuItem> items) {

        for (JMenuItem item : items)

            menu.addItem(item);

    }

    现在,任何人都可以用ArrayListLinkedList,甚至用Arrays.asList包装器包装的数组调用这个方法。

    注释:既然将集合接口作为方法参数是个很好的想法,为什么Java类库不更多地这样做呢?例如,JComboBox又有两个构造器:

    JComboBox(Object[] items)

    JConboBox(Vector<?> items}

    之所以没有这样做,原因很简单:时间问题。Swing类库是在集合类库之前创建的。

    如果编写了一个返回集合的方法,可能还想要一个返回接口,而不是返回类的方法,因为这样做可以在日后改变想法,并用另一个集合重新实现这个方法。例如,编写一个返回所有菜单项的方法getAllItems

    List<MenuItem> getAllItems(JMenu menu) {

        ArrayList<MenuItem> items = new ArrayList<MenuItem>();

        for (int i = 0; i < menu.getItemCount(); i++)

            items.add(menu.getItem(i));

        return items;

    }

    日后,可以做出这样的决定:不复制所有的菜单项,而仅仅提供这些菜单项的视图。要做到这一点,只需要返回AbstractList的匿名子类。

    List<MenuItem> getAllItems(JMenu menu) {

        return new AbstractList<MenuItem>() {

            public MenuItem get(int i) {

                return item.getItem(i);

            }

 

            public int size() {

                return item.getItemCount();

            }

        };

    }

    当然,这是一项高级技术,如果使用它,就应该将它支持的那些“可选”操作准确地记录在文档中。在这种情况下,必须提醒调用者返回的对象是一个不可修改的列表。
【总结】Java核心技术36讲知识点大纲 前段时间在极客时间上购买了杨晓峰老师的《Java核心技术36讲》,趁着这段时间有空,对相关知识点做了一个整体的目录,也对自己所掌握的Java基础进行了一个复习和梳理,若想深入学习,可以购买相关专栏,也可以对着该知识点查阅相关资料,源码等自学。—-kayfen 文末附专栏链接 Java核心技术36讲知识点总结 1 Java平台的理解 Java的特性,解释运行和编译运行 2 E... 阅读详情

相关推荐

JAVA核心技术 I :基础知识 (原书8).(美)昊斯特曼.part2.rar

2014.7.7亲测可用 请连同Part1下载后 解压即可阅读 高清pdf。

Java核心技术 I 基础知识(原书8)

Java核心技术 I 基础知识(原书8)

Java核心技术(原书8)I_基础知识 完整pdf

Java核心技术(原书8)I_基础知识 完整pdf 中文影印,比较清楚,因为只能上传48M的文件,故分2下载,这是第一部分

(strchi上传)Java核心技术(原书8)I_基础知识 完整pdf 2/2

Java核心技术(原书8)I_基础知识 完整pdf 中文影印,比较清楚,因为现有的都是分下载,现提供完整下载。因为只能上传48M的文件,故分下载 第一部分:http://download.csdn.net/detail/chi8/3697140

第9章 集合

第9章 集合 Java标准库中的集合类,用以数据结构(Data Structures) 9.1 Java集合框架 9.1.1 将集合的接口与实现分离 Java集合类库将接口(interface)与实现(implementation)分离。 例如队伍接口的最简形式可能类似下面这样: public interface Queue<E> { void add(E element); E remove(); int size(); } 这个接口没有说明队伍是如何实现的,每一个实现都可

qq_36178962的博客 690

Java核心技术(原书第10) Ⅰ》阅读心得与总结

放假前打算花利用假期时间阅读一下《Java核心技术 Ⅰ》(以下简称“Ⅰ”),终于花了一个月的时间粗细兼并地读完了这本书。今天抽点儿时间写个博客,总结一下阅读成果好了。 这本书给人最大的感受是知识讲得很专业,比本科教材内容充实几倍,豆瓣目前评分7.6,褒贬不一。先说说缺点吧,个人认为阅读体验并不是很好。主要体现在: 基础知识部分介绍很详细,几乎无死角,内容跟得上时代,但正因为这样,有些地方实在难以理解; 各章节间耦合性较低,知识没有问题,但逻辑安排似乎有点儿问题,不懂作者为何这样安排...

wenjingyu的专栏 851

Java核心技术 1 基础知识

网站 更多书籍点击进入>> CiCi岛 下载 电子仅供预览及学习交流使用,下载后请24小时内删除,支持正,喜欢的请购买正书籍 电子书下载(皮皮云盘-点击“普通下载”) 购买正 封页 编辑推荐 Java领域*有影响力和价值的著作之一,与《Java编程思想》齐名,10余年全球畅销不衰,广受好评   根据Java SE 8全面更新,系统全面讲解Java语言的核心概念、语法...

CiCi岛的专栏 2万+

Java核心技术(原书8)I_基础知识简要摘要(一)

Java核心技术(原书8)I_基础知识简要摘要(一) 1.javac程序是一个Java编译器。它将文件Welcom.java编译成Welcom.class,并发送到Java虚拟机。虚拟机执行编译器存放在class文件中的字节码。 2.类是构建所有Java应用程序和applet的构建块。Java应用程序中的全部内容都必须放置在类中。 3.关键字class后面紧跟类名。Java中定义类名的规则很...

daixuhui845270247的博客 348

Java核心技术1:基础知识原书第10

本书为专业程序员解决实际问题而写,Java基础知识面覆盖很完整,可以帮助你深入了解Java语言和库。在I中,Horstmann主要强调基本语言概念和现代用户界面编程基础,深入介绍了从Java面向对象编程到泛型、集合、lambda表达式、Swing UI设计以及并发和函数式编程的新方法等内容。 目录 译者序 前言 致谢 第1章 Java程序设计概述 1 第2章 Java程序设计环境 12 第3章 Java的基本程序设计结构 284章 对象与类 91 第5章 继承 147 第6章 接口、lambd

持 开源思想,撰 必胜所学,望 勤学者,无难处 4171

Java核心技术I:基础知识(原书8):13.3 集合框架

铁文整理 13.3 集合框架     框架是一个类的集,它奠定了创建高级功能的基础。框架包含很多超类,这些超类拥有非常有用的功能、策略和机制。框架使用者创建的子类可以扩展超类的功能,而不必重新创建这些基本的机制。例如,Swing就是一种用户界面的机制。     Java集合类库构成了集合类的框架。它为集合的实现者定义了大量的接口和抽象类(见图13-10),并且对其中的某些机制给予了描

tiewen的专栏 914

Java核心技术 I:基础知识原书8)》勘误表

limuyuan2008的专栏 954

Core Java最新 Ⅱ 上市!快来看看有哪些重要更新!

Core Java最新 Ⅱ 上市!快来看看有哪些重要更新!

小虚竹的专栏 1万+

哇哦!《Java核心技术》都更到12了,牛逼!

Java 已经诞生 27 年,在全球范围内拥有超过 1200 万的开发者群体,有超过 450 亿物理设备使用着 Java 技术,云端数据中心的虚拟化环境里,还运行着超过 250 亿个 Java 虚拟机的进程实例……这些数据都是 Java 过去取得巨大成就的佐证。进入云原生时代以后,传统的 Java 技术正遭受诸多挑战,“一次编写,到处运行”等植根于 Java 基因里的前提...

公众号-老炮说Java 8335

Java 核心技术 1PDF

Java 核心技术解析:面向对象编程与设计模式 Java 是一种广泛使用的编程语言,其核心技术的掌握对于软件开发尤为重要。《Java 核心技术 1》深入探讨了 Java基础知识,包括面向对象编程、异常处理、泛型等。本篇文章将围绕这些概念,结合代码示例进行科普,旨在帮助读者更好地理解 Java核心技术。 一、面向对...

weixin_35814757的博客 705

Java核心技术I:基础知识(原书8):13.2 具体的集合

铁文整理 13.2 具体的集合     这里并不打算更加详细地介绍所有的接口,但我们认为先介绍一下Java类库提供的具体数据结构还是很有用途的。透彻地介绍了人们想使用的类之后,再回过头研究一些抽象的概念,看一看集合框架组织这些类的方式,表13-1展示了Java类库中的集合,并简要描述了每个集合类的用途(鉴于简单起见,省略了将在第十四章中介绍的线程安全集合)。在表13-1中,除了以Map

tiewen的专栏 1783

编程语言书单(Java核心技术篇)

导语:2020年的春节假期注定是毕生难忘的,在家躺着为国家做贡献的程序员们,小编双手奉上Java核心技术程序员必读之热门书单,为你年后进大厂助力! 入门必读《Java核心技术 ...

华章IT官方博客 275

JAVA核心技术·I》第三章——JAVA的基本程序设计结构

🔥本篇概览:详细讲解了JAVA的基本程序设计结构常用库函数等。🌈⭕🔥

做一个善良的人,做好自己的事,如果愿意,再发一份光。 829

JAVA核心技术·I》——第三章13——Arrays类的所有方法

🔥本篇概览:详细讲解了Arrays类的常用方法。🌈⭕🔥

做一个善良的人,做好自己的事,如果愿意,再发一份光。 757

Java程序员必看的 13Java 书籍

关乎于程序员,除了做项目来提高自身的技术,还有一种提升自己的专业技能就是:多!看!书! 毕竟,书是学习的海洋呢!So,Java程序员你们准备好了吗?双手奉上Java程序员必读之热门书单。 在下面这 13 本书籍中小编还是要建议大家可以先去关注英文原,毕竟翻译的书籍中都有一定的语法错误。 入门 《Head First Java》 作者: Kathy Sierra / Bert ...

csdn_life18的博客 565
上一篇: Java核心技术卷I:基础知识(原书第8版):13.5 遗留的集合
下一篇: 名词王国里的死刑
铁文
博客等级 码龄22年 441粉丝 148原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值