话不多说,开始今天的学习,今天来到数据抽象这一章节,果然算法这本书,真的非常适合算法初学者,在java语言中,数据抽象即将数据视为某种对象,能很好的,更直观的的处理自己的数据。
首先来整理一下核心知识点:
1.2.1:
①为什么我们要使用抽象数据类型呢:这有一部分就是java封装的思想,你不需要知道我内部是怎么实现的,我给你相应的API,你直接调用就好,能输入你想存放的数据,返回你想要的数据。
②抽象数据类型在java继承下的得益:通过继承Object,数据类型会带有toString(),equals(),compareTo(),hashcode()等初始方法,我们通常会对其进行重载,转化为适合实际情况的方法。
③如何去使用ADT(abstract data type)呢:本质上来讲,ADT还是一种java类,我们要使用它,我们就必须先对他进行实例化(实例过程中,我么可以通过构造器对它进行成员属性初始化),然后就是调用实例化方法去操作成员属性了。
④实际应用场合:我们会先实例化一个或多个ADT,在程序逻辑中需要对数据进行修改时,调用实例化对象的方法,对对象的成员变量进行修改,当将ADT当成方法参数时,跟普通数据一样,只是取值的时候用下方法就好了。
1.2.2-12.4:
下面就讲述一些ADT的实际例子,以及具体如何实现,如果有一定java基础的同学可以大概看一下,没有的话还是仔细看看吧。
1.2.5:
ADT在设计时会用到的的一些原则:
①设计API时,我们应该考虑其实现难度,尽量别设计过于困难的功能
②注意使用范围,针对某一固定范畴进行设计
③要将算法实现和用例代码进行隔离,解耦合
④ADT的两种生成方式,实现接口,继承父类
⑤等价性原则:判断变量是否等价时会考虑到以下性质:自反性 自己要等于自己;对称性 x==y,则y==x;传递性 x==y,y==z,则x==z;一致性 当x和y没有变化时,x==y的结果不会变 ;非空性x==null一定要返回false
⑥不可变性:有部分ADT是不容许改变的,当一开始实例化调用构造器时,确认了某一层成员变量后是不会再发生改变的(for example:String类型,在实例化String的时候,后面的字符串将会保留在常量池,是没办法再改变的,而当我们改变String时,其实我们是在更改它的引用对象,又例如Vector,Vector生成之后数组成员的引用地址都是不能够变的,但是我们还得考虑一个问题,虽然引用地址不变,但是他们所指向的对象不一定是不能改变的,基本数据类型的一般不变,他们没有引用,只有数值摆在那块,而其他数据类型就不保准了)
⑦小知识点:断言,用来检测某一boolean数值是否为true,若不是则报错。
好了,又到了我最喜欢的练手时间了
1.2.1:初略说下思路,先定义ADT点,然后就是生成一个框,随机在框中生成点,放入Arraylist中,在逐一计算比较,其实我一开始也在想有没有什么更加优化的算法,我这设计的算法在点比较少的时候是没有问题的,当点多了,这样比较起来就有点蠢了,应该可一采用分割,将不同板块进行分割,再算最短,虽然这样只能算出局部最优,因为板块分割部位可能出现把最近的两个点切开了,以后再思考这个吧,先给出一种想法。
1.2.2:这个问题就比较简单了:先是标准输入(x1,x2),再按x1排序,当后面组的x1小于当前组的x2时,重合,继续向下比对,好像说的有点不太清楚,画个图吧
图好丑,各位不要介意,这个意思到了就好
1.2.3:2D间隔的重合,覆盖问题,,我们先根据p47,编写相应的ADT,然后就是上代码,让小伙伴自己看吧
public static void get2DN(int N,double min,double max) {
StdDraw.setXscale(min, max);
StdDraw.setYscale(min, max);
Point2D[] leftTopPoints = new Point2D[N];
Point2D[] rightBottomPoints = new Point2D[N];
Interval2D[] intervals = new Interval2D[N];
for (int i = 0; i < N; i++) {
//生成长x的过程
double a = StdRandom.uniform(min, max);
double b = StdRandom.uniform(min, max);
double left, right, top, bottom;
Interval1D x, y;
if (a < b) {
left = a;
right = b;
} else {
left = b;
right = a;
}
x = new Interval1D(left, right);
//生成宽y的过程
a = StdRandom.uniform(min, max);
b = StdRandom.uniform(min, max);
if (a < b) {
top = a;
bottom = b;
} else {
top = b;
bottom = a;
}
y = new Interval1D(top, bottom);
//将2d间隔的左上角和右下角这两个点保存下来,用作后面的包含判断
leftTopPoints[i] = new Point2D(left, top);
rightBottomPoints[i] = new Point2D(right, bottom);
intervals[i] = new Interval2D(x, y);
intervals[i].draw();
}
int containNum = 0, intervalNum = 0;
//开始判断重合和包含关系
for (int i = 0; i < N - 1; i++) {
for (int j = 0; j < N; j++) {
if (j > i && intervals[i].intersect(intervals[j])) {
intervalNum++;
}
if (j != i && intervals[i].contains(leftTopPoints[j]) && intervals[i].contains(rightBottomPoints[j])) {
containNum++;
}
}
}
System.out.println("Interval count: " + intervalNum);
System.out.println("Contain count: " + containNum);
}
1.2.6:回环变位,上代码吧
这是比较笨的版本
public static void circleString(String s1,String s2){
//先获取s1的第一个字符
char first=s1.charAt(0);
int find=0;
String s;
int fromindex=1;
//先看看人家是不是啥都没变,直接塞给我
if(s1.equals(s2)){
System.out.println("true");
return;
}
//大概讲讲思路,先获取s1第一个字符,然后从s2中找位置,拆串重新组装,比较,不行就往后继续找位置,以此类推到找不到就结束
while(find>=0){
find=s2.indexOf(first,fromindex);
if (find > 0) {
s=s2.substring(find);
s=s+s2.substring(0,find);
if(s.equals(s1)){
System.out.println("true"+find);
return;
}
}
if(fromindex<s1.length()){
fromindex=find+1;
}else{
System.out.println("false");
}
}
}
聪明版
public static void circleString2(String s1,String s2) {
int index=0;
//思路:将两个s1拼在一起,若果s2是s1的回环变位,则s1能包含s2
if(s1.length()==s2.length()&&(index=s1.concat(s1).indexOf(s2))>=0){
System.out.println("找到了"+index);
}
}
1.2.10:
public class VisualCounter {
int N;
int max;
int now=0;
public VisualCounter(int n, int max) {
this.N = n;
this.max = max;
}
//增长过程要通过判断,才能进行增长
public void increasement(int i){
if(now+i<=max&&N>0){
now+=i;
show();
}else if(Math.abs(now+i)>max){
System.out.println("超出额定数字");
}else if(N==0){
System.out.println("操作次数已用完");
}
}
//在画面上显示现在的now值
public void show(){
StdDraw.clear();
StdDraw.text(0.5,0.5,((Integer)now).toString());
}
}
1.2.16和1.2.17:
这个问题我贴部分代码吧,整体代码有点长,因为用到了断言,所以不支持在编译器下直接运行,需要在cmd 上用java -ea执行
public class Rational {
public int numerater;
public int denominator;
public Rational(int numerater, int denominator) {
int c=0;
if((c=day02.gcd(numerater,denominator))>1){
numerater/=c;
denominator/=c;
}
this.numerater = numerater;
this.denominator = denominator;
}
public Rational plus(Rational r){
Rational result;
long resultN=0;
long resultD=0;
int c=0;
resultN=this.numerater*r.denominator+r.numerater*this.denominator;
resultD=(long)this.denominator*(long)r.denominator;
boolean flag=resultD<2147483647;
try{
assert flag: "程序错误";
System.out.println("程序正常");
}catch(AssertionError err){
System.out.println(err.getMessage());
}
if((c=day02.gcd((int)resultN,(int)resultD))>1){
resultN/=c;
resultD/=c;
}
result=new Rational((int)resultN,(int)resultD);
return result;
}
}
1.2.18:
关于这个算法,首先我能告诉大家,它是错的,下面我们来分析一下其中的原因(看代码)
public class Accumulator {
private double m;
private double s;
private int N;
public void addDataValue(double x){
//每次添加新的数值,N+1
N++;
//这是整个算法错误的问题所在,方差运算需要的是所有数值的平均数,而不能这样逐个获得平均值去计算
s = s+1.0*(N-1)/N*(x-m)*(x-m);
m=m+(x-m)/N;
}
public double mean(){
return m;
}
public double var(){
return s/(N-1);
}
public double stddev(){
return Math.sqrt(this.var());
}
}
1.2.19:唉。。。基础题,不写了