一、数据抽象简介
1、数据类型指的是一组值和一组对这些值操作的集合,在Java之中原始数据类型:例如,原始数据类型int的取值范围是-231到231-1之间的整数,int的操作包括+、*、-、/、%、<和>。原则上所有程序都只需要使用原始数据类型即可,但在更高层次的抽象上编写程序会更加方便,学习定义和使用数据类型,称为数据抽象。
2、Java编程的基础主要是使用class关键字构造被称为引用类型的数据类型。这种编程风格也称为面向对象编程,因为它的核心概念是对象,即保存了某个数据类型的值的实体。如果只有Java的原始数据类型,我们的程序会在很大程度上被限制在算术计算上,但有了引用类型,我们就能编写操作字符串、图像、声音以及Java的标准库中或者本书的网站上的数百种抽象类型的程序。比各种库中预定义的数据类型更重要的是Java编程中的数据类型的种类是无限的,因为你能够定义自己的数据类型来抽象任意对象。
3、抽象数据类型(ADT)是一种能够对使用者隐藏数据表示的数据类型。用Java类来实现抽象数据类型和用一组静态方法实现一个函数库并没有什么不同。**抽象数据类型的主要不同之处在于它将数据和函数的实现关联,并将数据的表示方式隐藏起来。**在使用抽象数据类型时,我们的注意力集中在API描述的操作上而不会去关心数据的表示;在实现抽象数据类型时,我们的注意力集中在数据本身并将实现对该数据的各种操作。
抽象数据类型之所以重要是因为在程序设计上它们支持封装。我们将通过它们:
以适用于各种用途的API形式准确地定义问题
用API的实现来描述算法和数据结构
我们 研究同一个问题的没算法主要原因在于它们的性能特点不同,抽象数据类型正适合于对算法的研究,因为它确保我们可以随时将算法性能的知识应用于实践之中:可以在不修改任何用例代码的情况下用一种算法替换另一种算法并改进所有用例的性能。
二、使用抽象数据类型
要使用一种数据类型并不一定非得知道它是如何实现的,所以我们首先来编写一个使用一种名为Counter(计数器)的简单数据类型的程序。它的值是一个名称和一个非负整数,它的操作有创建对象并初始化为0、当前值加1和获取当前值。这个抽象对象在许多场景中都会用到。例如,这样一个数据类型可以用于电子记票软件,它能够保证投票者所能进行的唯一操作就是将他选择的候选人的计数器加一。我们也可以在分析算法性能时使用Counter来记录基本操作的调用次数。要使用Counter对象,首先需要了解应该如何定义数据类型的操作,以及在Java语言中应该如何创建和使用某个数据类型的对象,这些机制在现代编程中都非常重要。
1、 抽象数据类型的API
我们使用应用程序编程接口(API)来说明抽象数据类型的行为。它将列出所有构造函数和实例方法(即操作)并简要描述它们的功用,如图【Counter的API】所示。
尽管数据类型定义的基础是一组值的集合,但在API可见的仅是对它们的操作,而非它们的意义。因此,抽象数据类型的定义和静态方法库之间有许多共同之处:
❏两者的实现均为Java类;
❏实例方法可能接受0个或多个指定类型的参数,由括号表示并由逗号分隔;
❏它们可能会返回一个指定类型的值,也可能不会(用void表示)。
当然,它们也有三个显著的不同:
❏API中可能会出现若干个名称和类名相同且没有返回值的函数。这些特殊的函数被称为构造函数。在本例中,Counter对象有一个接受一个String参数的构造函数。
❏实例方法不需要static关键字。它们不是静态方法——它们的目的就是操作该数据类型中的值。
❏某些实例方法的存在是为了尊重Java的习惯——我们将此类方法称为继承的方法并在API中将它们显示为灰色。
和静态方法库的API一样,抽象数据类型的API也是和用例之间的一份契约,因此它是开发任何用例代码以及实现任意数据类型的起点。在本例中,这份API告诉我们可以通过构造函数Counter()、实例方法increment()和tally(),以及继承的toString()方法使用Counter类型的对象。
2、继承的方法
根据Java的约定,任意数据类型都能通过在API中包含特定的方法从Java的内在机制中获益。例如,Java中的所有数据类型都会继承toString()方法来返回用String表示的该类型的值。Java会在用+运算符将任意数据类型的值和String值连接时调用该方法。该方法的默认实现并不实用(它会返回用字符串表示的该数据类型值的内存地址),因此我们常常会提供实现来重载默认实现,并在此时在API中加上toString()方法。此类方法的例子还包括equals()、compareTo()和hashCode()。
3、用例代码
和基于静态方法的模块化编程一样,API允许我们在不知道实现细节的情况下编写调用它的代码(以及在不知道任何用例代码的情况下编写实现代码)。将程序组织为独立模块的机制可以应用于所有的Java类,因此它对基于抽象数据类型的模块化编程与对静态函数库一样有效。这样,只要抽象数据类型的源代码.java文件和我们的程序文件在同一个目录下,或是在标准Java库中,或是可以通过import语句访问,或是可以通过classpath机制访问,该程序就能够使用这个抽象数据类型,模块化编程的所有优势就都能够继续发挥。通过将实现某种数据类型的全部代码封装在一个Java类中,我们可以将用例代码推向更高的抽象层次。在用例代码中,你需要声明变量、创建对象来保存数据类型的值并允许通过实例方法来操作它们。尽管你也会注意到它们的一些相似之处,但这种方式和原始数据类型的使用方式非常不同。
4、对象
一般来说,可以声明一个变量heads并将它通过以下代码和Counter类型的数据关联起来:Counter heads;
但如何为它赋值或是对它进行操作呢?这个问题的答案涉及数据抽象中的一个基础概念:对象是能够承载数据类型的值的实体。**所有对象都有三大重要特性:状态、标识和行为。**对象的状态即数据类型中的值。对象的标识能够将一个对象区别于另一个对象。可以认为对象的标识就是它在内存中的位置。对象的行为就是数据类型的操作。数据类型的实现的唯一职责就是维护一个对象的身份,这样用例代码在使用数据类型时只需遵守描述对象行为的API即可,而无需关注对象状态的表示方法。对象的状态可以为用例代码提供信息,或是产生某种副作用,或是被数据类型的操作所改变。但数据类型的值的表示细节和用例代码是无关的。
引用是访问对象的一种方式,Java使用术语引用类型以示和原始数据类型(变量和值相关联)的区别。不同的Java实现中引用的实现细节也各不相同,但可以认为引用就是内存地址,如图【对象的表示】所示:
5、创建对象
每种数据类型中的值都存储于一个对象中。要创建(或实例化)一个对象,我们用关键字new并紧跟类名以及()(或在括号中指定一系列的参数,如果构造函数需要的话)来触发它的构造函数。构造函数没有返回值,因为它总是返回它的数据类型的对象的引用。每当用例调用了new(),系统都会:
❏为新的对象分配内存空间;
❏调用构造函数初始化对象中的值;
❏返回该对象的一个引用;
在用例代码中,我们一般都会在一条声明语句中创建一个对象并通过将它和一个变量关联来初始化该变量,和使用原始数据类型时一样。和原始数据类型不同的是,变量关联的是指向对象的引用而并非数据类型的值本身。我们可以用同一个类创建无数对象——每个对象都有自己的标识,且所存储的值和另一个相同类型的对象可以相同也可以不同。例如,以下代码创建了两个不同的Counter对象:
Counter heads = new Counter("heads");
Counter tails = new Counter("tails");
抽象数据类型向用例隐藏了值的表示细节,可以假定每个Counter对象中的值是一个String类型的名称和一个int计数器,但不能编写依赖于任何特定表示方法的代码对象的创建过程,如图【创建对象】所示:
6、调用实例方法
实例方法的意义在于操作数据类型中的值,因此Java语言提供了一种特别的机制来触发实例方法,它突出了实例方法和对象之间的联系。具体来说,我们调用一个实例方法的方式是先写出对象的变量名,紧接着是一个句点,然后是实例方法的名称,之后是0个或多个在括号中并由逗号分隔的参数。实例方法可能会改变数据类型中的值,也可能只是访问数据类型中的值。实例方法拥有我们静态方法的所有性质——参数按值传递,方法名可以被重载,方法可以有返回值,它们也许还会产生一些副作用。但它们还有一个特别的性质:方法的每次触发都是和一个对象相关的。例如,以下代码调用了实例方法increment()来操作Counter对象heads(在这里该操作会将计数器的值加1):
heads.increment();
而以下代码会调用实例方法tally()两次,第一次操作的是Counter对象heads,第二次是Counter对象tails(这里该操作会返回计数器的int值):
heads.tally() - tails.tally();
示例调用过程如图【触发实例方法的过程】所示:
在用例中实例方法和静态方法的调用方式完全相同——可以通过语句(void方法)也可以通过表达式(有返回值的方法)。静态方法的主要作用是实现函数;非静态(实例)方法的主要作用是实现数据类型的操作。两者都可能出现在用例代码中,但很容易就可以区分它们,因为静态方法调用的开头是类名(按习惯为大写),而非静态方法调用的开头总是对象名(按习惯为小写)。表【实例方法与静态方法】总结了这些不同之处:
7、使用对象
通过声明语句可以将变量名赋给对象,在代码中,我们不仅可以用该变量创建对象和调用实例方法,也可以像使用整数、浮点数和其他原始数据类型的变量一样使用它。要开发某种给定数据类型的用例,我们需要:
- 声明该类型的变量,以用来引用对象;
- 使用关键字new触发能够创建该类型的对象的一个构造函数;
- 使用变量名在语句或表达式中调用实例方法;
下面用例代码中的Flips类就使用了Counter类;它接受一个命令行参数T并模拟T次掷硬币(它还调用了StdRandom类)。除了这些直接用法外,还可以和使用原始数据类型的变量一样使用和对象关联的变量: - 赋值语句;
- 向方法传递对象或是从方法中返回对象;
- 创建并使用对象的数组;
public class Flips {
public static void main( String[] args ) {
StdOut.println(args.length);
int T = 5;
Counter heads = new Counter("heads");
Counter tails = new Counter("tails");
for (int t = 0;t<T;t++){
if(StdRandom.bernoulli(0.5)){
heads.increment();
}else {
tails.increment();
}
}
StdOut.println(heads);
StdOut.println(tails);
StdOut.println("delta:"+Math.abs(heads.tally()-tails.tally()));
}
}
逐个分析,你会发现,你需要从引用而非值的角度去考虑问题才能理解这些用法的行为;
其中:StdOut和StdRandom完整代码如下:
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.Locale;
public final class StdOut {
// force Unicode UTF-8 encoding; otherwise it's system dependent
private static final String CHARSET_NAME = "UTF-8";
// assume language = English, country = US for consistency with StdIn
private static final Locale LOCALE = Locale.US;
// send output here
private static PrintWriter out;
// this is called before invoking any methods
static {
try {
out = new PrintWriter(new OutputStreamWriter(System.out, CHARSET_NAME), true);
}
catch (UnsupportedEncodingException e) {
System.out.println(e);
}
}
// don't instantiate
private StdOut() { }
/**
* Terminates the current line by printing the line-separator string.
*/
public static void println() {
out.println();
}
/**
* Prints an object to this output stream and then terminates the line.
*
* @param x the object to print
*/
public static void println(Object x) {
out.println(x);
}
/**
* Prints a boolean to standard output and then terminates the line.
*
* @param x the boolean to print
*/
public static void println(boolean x) {
out.println(x);
}
/**
* Prints a character to standard output and then terminates the line.
*
* @param x the character to print
*/
public static void println(char x) {
out.println(x);
}
/**
* Prints a double to standard output and then terminates the line.
*
* @param x the double to print
*/
public static void println(double x) {
out.println(x);
}
/**
* Prints an integer to standard output and then terminates the line.
*
* @param x the integer to print
*/
public static void println(float x) {
out.println(x);
}
/**
* Prints an integer to standard output and then terminates the line.
*
* @param x the integer to print
*/
public static void println(int x) {
out.println(x);
}
/**
* Prints a long to standard output and then terminates the line.
*
* @param x the long to print
*/
public static void println(long x) {
out.println(x);
}
/**
* Prints a short integer to standard output and then terminates the line.
*
* @param x the short to print
*/
public static void println(short x) {
out.println(x);
}
/**
* Prints a byte to standard output and then terminates the line.
* <p>
* To write binary data, see {@link BinaryStdOut}.
*
* @param x the byte to print
*/
public static void println(byte x) {
out.println(x);
}
/**
* Flushes standard output.
*/
public static void print() {
out.flush();
}
/**
* Prints an object to standard output and flushes standard output.
*
* @param x the object to print
*/
public static void print(Object x) {
out.print(x);
out.flush();
}
/**
* Prints a boolean to standard output and flushes standard output.
*
* @param x the boolean to print
*/
public static void print(boolean x) {
out.print(x);
out.flush();
}
/**
* Prints a character to standard output and flushes standard output.
*
* @param x the character to print
*/
public static void print(char x) {
out.print(x);
out.flush();
}
/**
* Prints a double to standard output and flushes standard output.
*
* @param x the double to print
*/
public static void print(double x) {
out.print(x);
out.flush();
}
/**
* Prints a float to standard output and flushes standard output.
*
* @param x the float to print
*/
public static void print(float x) {
out.print(x);
out.flush();
}
/**
* Prints an integer to standard output and flushes standard output.
*
* @param x the integer to print
*/
public static void print(int x) {
out.print(x);
out.flush();
}
/**
* Prints a long integer to standard output and flushes standard output.
*
* @param x the long integer to print
*/
public static void print(long x) {
out.print(x);
out.flush();
}
/**
* Prints a short integer to standard output and flushes standard output.
*
* @param x the short integer to print
*/
public static void print(short x) {
out.print(x);
out.flush();
}
/**
* Prints a byte to standard output and flushes standard output.
*
* @param x the byte to print
*/
public static void print(byte x) {
out.print(x);
out.flush();
}
/**
* Prints a formatted string to standard output, using the specified format
* string and arguments, and then flushes standard output.
*
*
* @param format the <a href = "http://docs.oracle.com/javase/7/docs/api/java/util/Formatter.html#syntax">format string</a>
* @param args the arguments accompanying the format string
*/
public static void printf(String format, Object... args) {
out.printf(LOCALE, format, args);
out.flush();
}
/**
* Prints a formatted string to standard output, using the locale and
* the specified format string and arguments; then flushes standard output.
*
* @param locale the locale
* @param format the <a href = "http://docs.oracle.com/javase/7/docs/api/java/util/Formatter.html#syntax">format string</a>
* @param args the arguments accompanying the format string
*/
public static void printf(Locale locale, String format, Object... args) {
out.printf(locale, format, args);
out.flush();
}
}
import java.util.Random;
/**
* <p><b>Overview.</b>
* The {@code StdRandom} class provides static methods for generating
* random number from various discrete and continuous distributions,
* including uniform, Bernoulli, geometric, Gaussian, exponential, Pareto,
* Poisson, and Cauchy. It also provides method for shuffling an
* array or subarray and generating random permutations.
*
* <p><b>Conventions.</b>
* By convention, all intervals are half open. For example,
* <code>uniformDouble(-1.0, 1.0)</code> returns a random number between
* <code>-1.0</code> (inclusive) and <code>1.0</code> (exclusive).
* Similarly, <code>shuffle(a, lo, hi)</code> shuffles the <code>hi - lo</code>
* elements in the array <code>a[]</code>, starting at index <code>lo</code>
* (inclusive) and ending at index <code>hi</code> (exclusive).
*
* <p><b>Performance.</b>
* The methods all take constant expected time, except those that involve arrays.
* The <em>shuffle</em> method takes time linear in the subarray to be shuffled;
* the <em>discrete</em> methods take time linear in the length of the argument
* array.
*
* <p><b>Additional information.</b>
* For additional documentation,
* see <a href="https://introcs.cs.princeton.edu/22library">Section 2.2</a> of
* <i>Computer Science: An Interdisciplinary Approach</i>
* by Robert Sedgewick and Kevin Wayne.
*
* @author Robert Sedgewick
* @author Kevin Wayne
*/
public final class StdRandom {
private static Random random; // pseudo-random number generator
private static long seed; // pseudo-random number generator seed
// static initializer
static {
// this is how the seed was set in Java 1.4
seed = System.currentTimeMillis();
random = new Random(seed);
}
// don't instantiate
private StdRandom() {
}
/**
* Sets the seed of the pseudo-random number generator.
* This method enables you to produce the same sequence of "random"
* number for each execution of the program.
* Ordinarily, you should call this method at most once per program.
*
* @param s the seed
*/
public static void setSeed(long s) {
seed = s;
random = new Random(seed);
}
/**
* Returns the seed of the pseudo-random number generator.
*
* @return the seed
*/
public static long getSeed() {
return seed;
}
/**
* Returns a random real number uniformly in [0, 1).
*
* @return a random real number uniformly in [0, 1)
* @deprecated Replaced by {@link #uniformDouble()}.
*/
@Deprecated
public static double uniform() {
return uniformDouble();
}
/**
* Returns a random real number uniformly in [0, 1).
*
* @return a random real number uniformly in [0, 1)
*/
public static double uniformDouble() {
return random.nextDouble();
}
/**
* Returns a random integer uniformly in [0, n).
*
* @param n number of possible integers
* @return a random integer uniformly between 0 (inclusive) and {@code n} (exclusive)
* @throws IllegalArgumentException if {@code n <= 0}
* @deprecated Replaced by {@link #uniformInt(int n)}.
*/
@Deprecated
public static int uniform(int n) {
return uniformInt(n);
}
/**
* Returns a random integer uniformly in [0, n).
*
* @param n number of possible integers
* @return a random integer uniformly between 0 (inclusive) and {@code n} (exclusive)
* @throws IllegalArgumentException if {@code n <= 0}
*/
public static int uniformInt(int n) {
if (n <= 0) throw new IllegalArgumentException("argument must be positive: " + n);
return random.nextInt(n);
}
/**
* Returns a random long integer uniformly in [0, n).
*
* @param n number of possible {@code long} integers
* @return a random long integer uniformly between 0 (inclusive) and {@code n} (exclusive)
* @throws IllegalArgumentException if {@code n <= 0}
* @deprecated Replaced by {@link #uniformLong(long n)}.
*/
@Deprecated
public static long uniform(long n) {
return uniformLong(n);
}
/**
* Returns a random long integer uniformly in [0, n).
*
* @param n number of possible {@code long} integers
* @return a random long integer uniformly between 0 (inclusive) and {@code n} (exclusive)
* @throws IllegalArgumentException if {@code n <= 0}
*/
public static long uniformLong(long n) {
if (n <= 0L) throw new IllegalArgumentException("argument must be positive: " + n);
// https://docs.oracle.com/javase/8/docs/api/java/util/Random.html#longs-long-long-long-
long r = random.nextLong();
long m = n - 1;
// power of two
if ((n & m) == 0L) {
return r & m;
}
// reject over-represented candidates
long u = r >>> 1;
while (u + m - (r = u % n) < 0L) {
u = random.nextLong() >>> 1;
}
return r;
}
///
// STATIC METHODS BELOW RELY ON JAVA.UTIL.RANDOM ONLY INDIRECTLY VIA
// THE STATIC METHODS ABOVE.
///
/**
* Returns a random real number uniformly in [0, 1).
*
* @return a random real number uniformly in [0, 1)
* @deprecated Replaced by {@link #uniformDouble()}.
*/
@Deprecated
public static double random() {
return uniformDouble();
}
/**
* Returns a random integer uniformly in [a, b).
*
* @param a the left endpoint
* @param b the right endpoint
* @return a random integer uniformly in [a, b)
* @throws IllegalArgumentException if {@code b <= a}
* @throws IllegalArgumentException if {@code b - a >= Integer.MAX_VALUE}
* @deprecated Replaced by {@link #uniformInt(int a, int b)}.
*/
@Deprecated
public static int uniform(int a, int b) {
return uniformInt(a, b);
}
/**
* Returns a random integer uniformly in [a, b).
*
* @param a the left endpoint
* @param b the right endpoint
* @return a random integer uniformly in [a, b)
* @throws IllegalArgumentException if {@code b <= a}
* @throws IllegalArgumentException if {@code b - a >= Integer.MAX_VALUE}
*/
public static int uniformInt(int a, int b) {
if ((b <= a) || ((long) b - a >= Integer.MAX_VALUE)) {
throw new IllegalArgumentException("invalid range: [" + a + ", " + b + ")");
}
return a + uniform(b - a);
}
/**
* Returns a random real number uniformly in [a, b).
*
* @param a the left endpoint
* @param b the right endpoint
* @return a random real number uniformly in [a, b)
* @throws IllegalArgumentException unless {@code a < b}
* @deprecated Replaced by {@link #uniformDouble(double a, double b)}.
*/
@Deprecated
public static double uniform(double a, double b) {
return uniformDouble(a, b);
}
/**
* Returns a random real number uniformly in [a, b).
*
* @param a the left endpoint
* @param b the right endpoint
* @return a random real number uniformly in [a, b)
* @throws IllegalArgumentException unless {@code a < b}
*/
public static double uniformDouble(double a, double b) {
if (!(a < b)) {
throw new IllegalArgumentException("invalid range: [" + a + ", " + b + ")");
}
return a + uniform() * (b - a);
}
/**
* Returns a random boolean from a Bernoulli distribution with success
* probability <em>p</em>.
*
* @param p the probability of returning {@code true}
* @return {@code true} with probability {@code p} and
* {@code false} with probability {@code 1 - p}
* @throws IllegalArgumentException unless {@code 0} ≤ {@code p} ≤ {@code 1.0}
*/
public static boolean bernoulli(double p) {
if (!(p >= 0.0 && p <= 1.0))
throw new IllegalArgumentException("probability p must be between 0.0 and 1.0: " + p);
return uniformDouble() < p;
}
/**
* Returns a random boolean from a Bernoulli distribution with success
* probability 1/2.
*
* @return {@code true} with probability 1/2 and
* {@code false} with probability 1/2
*/
public static boolean bernoulli() {
return bernoulli(0.5);
}
/**
* Returns a random real number from a standard Gaussian distribution.
*
* @return a random real number from a standard Gaussian distribution
* (mean 0 and standard deviation 1).
*/
public static double gaussian() {
// use the polar form of the Box-Muller transform
double r, x, y;
do {
x = uniformDouble(-1.0, 1.0);
y = uniformDouble(-1.0, 1.0);
r = x * x + y * y;
} while (r >= 1 || r == 0);
return x * Math.sqrt(-2 * Math.log(r) / r);
// Remark: y * Math.sqrt(-2 * Math.log(r) / r)
// is an independent random gaussian
}
/**
* Returns a random real number from a Gaussian distribution with mean μ
* and standard deviation σ.
*
* @param mu the mean
* @param sigma the standard deviation
* @return a real number distributed according to the Gaussian distribution
* with mean {@code mu} and standard deviation {@code sigma}
*/
public static double gaussian(double mu, double sigma) {
return mu + sigma * gaussian();
}
/**
* Returns a random integer from a geometric distribution with success
* probability <em>p</em>.
* The integer represents the number of independent trials
* before the first success.
*
* @param p the parameter of the geometric distribution
* @return a random integer from a geometric distribution with success
* probability {@code p}; or {@code Integer.MAX_VALUE} if
* {@code p} is (nearly) equal to {@code 1.0}.
* @throws IllegalArgumentException unless {@code p >= 0.0} and {@code p <= 1.0}
*/
public static int geometric(double p) {
if (!(p >= 0)) {
throw new IllegalArgumentException("probability p must be greater than 0: " + p);
}
if (!(p <= 1.0)) {
throw new IllegalArgumentException("probability p must not be larger than 1: " + p);
}
// using algorithm given by Knuth
return (int) Math.ceil(Math.log(uniformDouble()) / Math.log(1.0 - p));
}
/**
* Returns a random integer from a Poisson distribution with mean λ.
*
* @param lambda the mean of the Poisson distribution
* @return a random integer from a Poisson distribution with mean {@code lambda}
* @throws IllegalArgumentException unless {@code lambda > 0.0} and not infinite
*/
public static int poisson(double lambda) {
if (!(lambda > 0.0))
throw new IllegalArgumentException("lambda must be positive: " + lambda);
if (Double.isInfinite(lambda))
throw new IllegalArgumentException("lambda must not be infinite: " + lambda);
// using algorithm given by Knuth
// see http://en.wikipedia.org/wiki/Poisson_distribution
int k = 0;
double p = 1.0;
double expLambda = Math.exp(-lambda);
do {
k++;
p *= uniformDouble();
} while (p >= expLambda);
return k - 1;
}
/**
* Returns a random real number from the standard Pareto distribution.
*
* @return a random real number from the standard Pareto distribution
*/
public static double pareto() {
return pareto(1.0);
}
/**
* Returns a random real number from a Pareto distribution with
* shape parameter α.
*
* @param alpha shape parameter
* @return a random real number from a Pareto distribution with shape
* parameter {@code alpha}
* @throws IllegalArgumentException unless {@code alpha > 0.0}
*/
public static double pareto(double alpha) {
if (!(alpha > 0.0))
throw new IllegalArgumentException("alpha must be positive: " + alpha);
return Math.pow(1 - uniformDouble(), -1.0 / alpha) - 1.0;
}
/**
* Returns a random real number from the Cauchy distribution.
*
* @return a random real number from the Cauchy distribution.
*/
public static double cauchy() {
return Math.tan(Math.PI * (uniformDouble() - 0.5));
}
/**
* Returns a random integer from the specified discrete distribution.
*
* @param probabilities the probability of occurrence of each integer
* @return a random integer from a discrete distribution:
* {@code i} with probability {@code probabilities[i]}
* @throws IllegalArgumentException if {@code probabilities} is {@code null}
* @throws IllegalArgumentException if sum of array entries is not (very nearly) equal to {@code 1.0}
* @throws IllegalArgumentException unless {@code probabilities[i] >= 0.0} for each index {@code i}
*/
public static int discrete(double[] probabilities) {
if (probabilities == null)
throw new IllegalArgumentException("argument array must not be null");
double EPSILON = 1.0E-14;
double sum = 0.0;
for (int i = 0; i < probabilities.length; i++) {
if (!(probabilities[i] >= 0.0))
throw new IllegalArgumentException("array entry " + i + " must be non-negative: " + probabilities[i]);
sum += probabilities[i];
}
if (sum > 1.0 + EPSILON || sum < 1.0 - EPSILON)
throw new IllegalArgumentException("sum of array entries does not approximately equal 1.0: " + sum);
// the for loop may not return a value when both r is (nearly) 1.0 and when the
// cumulative sum is less than 1.0 (as a result of floating-point roundoff error)
while (true) {
double r = uniformDouble();
sum = 0.0;
for (int i = 0; i < probabilities.length; i++) {
sum = sum + probabilities[i];
if (sum > r) return i;
}
}
}
/**
* Returns a random integer from the specified discrete distribution.
*
* @param frequencies the frequency of occurrence of each integer
* @return a random integer from a discrete distribution:
* {@code i} with probability proportional to {@code frequencies[i]}
* @throws IllegalArgumentException if {@code frequencies} is {@code null}
* @throws IllegalArgumentException if all array entries are {@code 0}
* @throws IllegalArgumentException if {@code frequencies[i]} is negative for any index {@code i}
* @throws IllegalArgumentException if sum of frequencies exceeds {@code Integer.MAX_VALUE} (2<sup>31</sup> - 1)
*/
public static int discrete(int[] frequencies) {
if (frequencies == null)
throw new IllegalArgumentException("argument array must not be null");
long sum = 0;
for (int i = 0; i < frequencies.length; i++) {
if (frequencies[i] < 0)
throw new IllegalArgumentException("array entry " + i + " must be non-negative: " + frequencies[i]);
sum += frequencies[i];
}
if (sum == 0)
throw new IllegalArgumentException("at least one array entry must be positive");
if (sum >= Integer.MAX_VALUE)
throw new IllegalArgumentException("sum of frequencies overflows an int");
// pick index i with probability proportional to frequency
double r = uniformInt((int) sum);
sum = 0;
for (int i = 0; i < frequencies.length; i++) {
sum += frequencies[i];
if (sum > r) return i;
}
// can't reach here
assert false;
return -1;
}
/**
* Returns a random real number from an exponential distribution
* with rate λ.
*
* @param lambda the rate of the exponential distribution
* @return a random real number from an exponential distribution with
* rate {@code lambda}
* @throws IllegalArgumentException unless {@code lambda > 0.0}
*/
public static double exp(double lambda) {
if (!(lambda > 0.0))
throw new IllegalArgumentException("lambda must be positive: " + lambda);
return -Math.log(1 - uniformDouble()) / lambda;
}
/**
* Rearranges the elements of the specified array in uniformly random order.
*
* @param a the array to shuffle
* @throws IllegalArgumentException if {@code a} is {@code null}
*/
public static void shuffle(Object[] a) {
validateNotNull(a);
int n = a.length;
for (int i = 0; i < n; i++) {
int r = i + uniformInt(n - i); // between i and n-1
Object temp = a[i];
a[i] = a[r];
a[r] = temp;
}
}
/**
* Rearranges the elements of the specified array in uniformly random order.
*
* @param a the array to shuffle
* @throws IllegalArgumentException if {@code a} is {@code null}
*/
public static void shuffle(double[] a) {
validateNotNull(a);
int n = a.length;
for (int i = 0; i < n; i++) {
int r = i + uniformInt(n - i); // between i and n-1
double temp = a[i];
a[i] = a[r];
a[r] = temp;
}
}
/**
* Rearranges the elements of the specified array in uniformly random order.
*
* @param a the array to shuffle
* @throws IllegalArgumentException if {@code a} is {@code null}
*/
public static void shuffle(int[] a) {
validateNotNull(a);
int n = a.length;
for (int i = 0; i < n; i++) {
int r = i + uniformInt(n - i); // between i and n-1
int temp = a[i];
a[i] = a[r];
a[r] = temp;
}
}
/**
* Rearranges the elements of the specified array in uniformly random order.
*
* @param a the array to shuffle
* @throws IllegalArgumentException if {@code a} is {@code null}
*/
public static void shuffle(char[] a) {
validateNotNull(a);
int n = a.length;
for (int i = 0; i < n; i++) {
int r = i + uniformInt(n - i); // between i and n-1
char temp = a[i];
a[i] = a[r];
a[r] = temp;
}
}
/**
* Rearranges the elements of the specified subarray in uniformly random order.
*
* @param a the array to shuffle
* @param lo the left endpoint (inclusive)
* @param hi the right endpoint (exclusive)
* @throws IllegalArgumentException if {@code a} is {@code null}
* @throws IllegalArgumentException unless {@code (0 <= lo) && (lo < hi) && (hi <= a.length)}
*/
public static void shuffle(Object[] a, int lo, int hi) {
validateNotNull(a);
validateSubarrayIndices(lo, hi, a.length);
for (int i = lo; i < hi; i++) {
int r = i + uniformInt(hi - i); // between i and hi-1
Object temp = a[i];
a[i] = a[r];
a[r] = temp;
}
}
/**
* Rearranges the elements of the specified subarray in uniformly random order.
*
* @param a the array to shuffle
* @param lo the left endpoint (inclusive)
* @param hi the right endpoint (exclusive)
* @throws IllegalArgumentException if {@code a} is {@code null}
* @throws IllegalArgumentException unless {@code (0 <= lo) && (lo < hi) && (hi <= a.length)}
*/
public static void shuffle(double[] a, int lo, int hi) {
validateNotNull(a);
validateSubarrayIndices(lo, hi, a.length);
for (int i = lo; i < hi; i++) {
int r = i + uniformInt(hi - i); // between i and hi-1
double temp = a[i];
a[i] = a[r];
a[r] = temp;
}
}
/**
* Rearranges the elements of the specified subarray in uniformly random order.
*
* @param a the array to shuffle
* @param lo the left endpoint (inclusive)
* @param hi the right endpoint (exclusive)
* @throws IllegalArgumentException if {@code a} is {@code null}
* @throws IllegalArgumentException unless {@code (0 <= lo) && (lo < hi) && (hi <= a.length)}
*/
public static void shuffle(int[] a, int lo, int hi) {
validateNotNull(a);
validateSubarrayIndices(lo, hi, a.length);
for (int i = lo; i < hi; i++) {
int r = i + uniformInt(hi - i); // between i and hi-1
int temp = a[i];
a[i] = a[r];
a[r] = temp;
}
}
/**
* Returns a uniformly random permutation of <em>n</em> elements.
*
* @param n number of elements
* @return an array of length {@code n} that is a uniformly random permutation
* of {@code 0}, {@code 1}, ..., {@code n-1}
* @throws IllegalArgumentException if {@code n} is negative
*/
public static int[] permutation(int n) {
if (n < 0) throw new IllegalArgumentException("n must be non-negative: " + n);
int[] perm = new int[n];
for (int i = 0; i < n; i++)
perm[i] = i;
shuffle(perm);
return perm;
}
/**
* Returns a uniformly random permutation of <em>k</em> of <em>n</em> elements.
*
* @param n number of elements
* @param k number of elements to select
* @return an array of length {@code k} that is a uniformly random permutation
* of {@code k} of the elements from {@code 0}, {@code 1}, ..., {@code n-1}
* @throws IllegalArgumentException if {@code n} is negative
* @throws IllegalArgumentException unless {@code 0 <= k <= n}
*/
public static int[] permutation(int n, int k) {
if (n < 0) throw new IllegalArgumentException("n must be non-negative: " + n);
if (k < 0 || k > n) throw new IllegalArgumentException("k must be between 0 and n: " + k);
int[] perm = new int[k];
for (int i = 0; i < k; i++) {
int r = uniformInt(i + 1); // between 0 and i
perm[i] = perm[r];
perm[r] = i;
}
for (int i = k; i < n; i++) {
int r = uniformInt(i + 1); // between 0 and i
if (r < k) perm[r] = i;
}
return perm;
}
// throw an IllegalArgumentException if x is null
// (x can be of type Object[], double[], int[], ...)
private static void validateNotNull(Object x) {
if (x == null) {
throw new IllegalArgumentException("argument must not be null");
}
}
// throw an exception unless 0 <= lo <= hi <= length
private static void validateSubarrayIndices(int lo, int hi, int length) {
if (lo < 0 || hi > length || lo > hi) {
throw new IllegalArgumentException("subarray indices out of bounds: [" + lo + ", " + hi + ")");
}
}
}
8、赋值语句
使用引用类型的赋值语句将会创建该引用的一个副本。赋值语句不会创建新的对象,而只是创建另一个指向某个已经存在的对象的引用。这种情况被称为别名:两个变量同时指向同一个对象。别名的效果可能会出乎你的意料,因为对于原始数据类型的变量,情况不同,你必须理解其中的差异。如果x和y是原始数据类型的变量,那么赋值语句x = y会将y的值复制到x中。对于引用类型,复制的是引用(而非实际的值)。在Java中,别名是bug的常见原因,如下例所示:
Counter c1 = new Counter("ones");
c1.increment();
Counter c2 = c1;
c2.increment();
StdOut.println(c1);
Counter的完整代码如下:
public class Counter {
private final String name ;
private int count;
public Counter(String id) {
this.name = id;
}
public void increment(){
count++;
}
public int tally(){
return count;
}
public String toString(){
return count +" "+name;
}
}
对于一般的toString()实现,这段代码将会打印出"2 ones"。可能并不是我们想要的,而且乍一看有些奇怪。这种问题经常出现在使用对象经验不足的人所编写的程序之中(可能就是你,所以请集中注意力!)。改变一个对象的状态将会影响到所有和该对象的别名有关的代码。我们习惯于认为两个不同的原始数据类型的变量是相互独立的,但这种感觉对于引用类型的变量并不适用。
9、 将对象作为参数
可以将对象作为参数传递给方法,这一般都能简化用例代码。例如,当我们使用Counter对象作为参数时,本质上我们传递的是一个名称和一个计数器,但我们只需要指定一个变量。当我们调用一个需要参数的方法时,该动作在Java中的效果相当于每个参数值都出现在了一个赋值语句的右侧,而参数名则在该赋值语句的左侧。也就是说,Java将参数值的一个副本从调用端传递给了方法,这种方式称为按值传递。这种方式的一个重要后果是方法无法改变调用端变量的值。对于原始数据类型来说,这种策略正是我们所期望的(两个变量互相独立),但每当使用引用类型作为参数时我们创建的都是别名,所以就必须小心。换句话说,这种约定将会传递引用的值(复制引用),也就是传递对象的引用。例如,如果我们传递了一个指向Counter类型的对象的引用,那么方法虽然无法改变原始的引用(比如将它指向另一个Counter对象),但它能够改变该对象的值,比如通过该引用调用increment()方法。
10、将对象作为返回值
当然也能够将对象作为方法的返回值。方法可以将它的参数对象返回,如下面的例子所示,也可以创建一个对象并返回它的引用。这种能力非常重要,因为Java中的方法只能有一个返回值——有了对象我们的代码实际上就能返回多个值:
public class FlipsMax {
public static Counter max(Counter x, Counter y) {
if (x.tally() > y.tally()) {
return x;
} else {
return y;
}
}
public static void main(String[] args) {
int T = 10;
Counter heads = new Counter("heads");
Counter tails = new Counter("tails");
for (int t = 0; t < T; t++) {
if (StdRandom.bernoulli(0.5)) {
heads.increment();
} else {
tails.increment();
}
}
if (heads.tally() == tails.tally()) {
StdOut.println("Tie");
} else {
StdOut.println(max(heads, tails) + " wins");
}
}
}
11、数组也是对象
在Java中,所有非原始数据类型的值都是对象。也就是说,数组也是对象。和字符串一样,Java语言对于数组的某些操作有特殊的支持:声明、初始化和索引。和其他对象一样,当我们将数组传递给一个方法或是将一个数组变量放在赋值语句的右侧时,我们都是在创建该数组引用的一个副本,而非数组的副本。对于一般情况,这种效果正合适,因为我们期望方法能够重新安排数组的条目并修改数组的内容,如java.util.Array.sort();
12、对象的数组
我们已经看到,数组元素可以是任意类型的数据:我们实现的main()方法的args[]参数就是一个String对象的数组。创建一个对象的数组需要以下两个步骤:
- 使用方括号语法调用数组的构造函数创建数组;
- 对于每个数组元素调用它的构造函数创建相应的对象;
如下面这段代码模拟的是掷骰子,它使用了一个Counter对象的数组来记录每种可能的值的出现次数。在Java中,对象数组即是一个由对象的引用组成的数组,而非所有对象本身组成的数组。如果对象非常大,那么在移动它们时由于只需要操作引用而非对象本身,这就会大大提高效率;如果对象很小,每次获取信息时都需要通过引用反而会降低效率。
public class Rolls {
public static void main(String[] args) {
int T =10;
int SIDES =6;
Counter[] rolls = new Counter[SIDES+1];
for (int i = 0; i <= SIDES; i++) {
rolls[i] = new Counter(i +"'s");
}
for (int i = 0; i < T; i++) {
int result = StdRandom.uniform(1,SIDES+1);
rolls[result].increment();
}
for (int i = 0; i < SIDES; i++) {
StdOut.println(rolls[i]);
}
}
}
有了这些对象的知识,运用数据抽象的思想编写代码(定义和使用数据类型,将数据类型的值封装在对象中)的方式称为面向对象编程。
总结:
数据类型指的是一组值和一组对值的操作的集合。我们会将数据类型实现在独立的Java类模块中并编写它们的用例。对象是能够存储任意该数据类型的值的实体,或数据类型的实例。对象有三大关键性质:状态、标识和行为。
一个数据类型的实现所支持的操作如下
❏创建对象(创造它的标识):使用new关键字触发构造函数并创建对象,初始化对象中的值并返回对它的引用。
❏操作对象中的值(控制对象的行为,可能会改变对象的状态):使用和对象关联的变量调用实例方法来对对象中的值进行操作。
❏操作多个对象:创建对象的数组,像原始数据类型的值一样将它们传递给方法或是从方法中返回,只是变量关联的是对象的引用而非对象本身。
参考《算法》