算法(读书笔记):1.关于算法的零散小点

1.2数据抽象

数据类型指的是一组值和一组对这些值的操作的集合。
原则上,所有程序都只需要使用原始数据类型即可,但在更高层次的抽象上编写程序会更加方便。
抽象数据类型(ADT)是一种能够对使用者隐藏数据表示的数据类型。
使用抽象数据类型时,我们的注意力集中在API描述的操作上而不会关心数据的表示;在实现抽象数据类型时,我们的注意力集中在数据本身并将实现对该数据的各种操作。

我们将:
1.以适用于各种用途的API形式准确定义问题。
2.用API的实现描述算法和数据结构。

我们将列出所有构造函数实例方法(即操作)并简要描述他们的功用。

和基于静态方法的模块化编程一样,API允许我们在不知道实现细节的情况下编程调用它的代码(以及在不知道任何用例代码的情况下编写实现代码)。

对象是能够承载数据类型的值的实体。
三个重要特性:状态标识行为

每当用例调用了new(),系统都会:
1.为新的对象分配内存空间
2.调用构造函数初始化对象中的值
3.返回该对象的一个引用

//将变量和对象的引用关联的声明语句
    //调用构造函数来创建一个对象
    //实际上调用构造函数返回的是引用
    Counter heads = new Counter("heads");

使用引用类型的赋值语句将会创建该引用的一个副本。赋值语句不会创建新的对象,而只是创建另一个指向某个已经存在的对象的引用。这种情况叫别名。在java中,别名是bug的常见原因。它使得,改变一个对象的状态将会影响到所有和该对象的别名有关的代码。

在java中,将对象作为返回值的能力非常重要。因为java中的方法只能有一个返回值–有了对象我们的代码实际上就能返回多个值。

在java中,所有非原始数据类型的值都是对象。在java中,对象数组即是一个由对象的引用组成的数组,而非所有对象本身组成的数组。如果对象非常大,那么移动它们时只需要操作引用而非对象本身,这就大大提高效率。但是,如果对象很小,每次获取信息时都需要通过引用反而降低效率。

java中String字符串:
一个String值是一串可以由索引访问的char值。String值和字符数组类似,当两者不同。了解某些方法的性能特点在开发字符串处理算法时非常重要。
为什么不直接使用字符数组代替String值呢?
对于任何抽象数据类型,这个答案都是一样的:为了使代码更加简洁清晰

数据类型的设计:
我们坚持将API作为用例和实现之间唯一的依赖点来做到这一点。
理想情况下,一份API应该能够清楚地说明所有可能的输入副作用

数据类型的API提供的太窄(缺少用例所需方法)或太宽(例如Stack,提供了过多的接口)。考虑设计标准:只为用例提供他们所需要的,仅此而已。

java内存管理:
我们可以为一个引用变量赋予新值,因此一段程序可能会产生一个无法被引用的对象。对象的唯一的引用被赋值语句覆盖后,由于无法再通过引用访问原对象,这样的对象将会变成孤儿对象在离开作用域以后也会变成孤儿

作为用例程序员,在使用java数组(可变)和java的String类型(不可变)时遇到了关于可变性的区别。将一个String传递给一个方法时,不用担心方法会改变字符串中的字符顺序,但当把一个数组传递给一个方法时,方法可以自由改变数组中的内容。当然,也有可变字符串(StringBuilder类)不可变数组(Vector类)

如果一个应用类型的实例变量含有修饰符final,该实例变量的值(某个对象的引用)就永远无法改变—它将永远指向同一个对象,但对象的值本身仍然可以改变

课后问答:
问:为什么要区别原始数据类型和引用类型?
答:因为性能。能用原始数据类型尽量用原始数据类型。

问:指针是什么?
答:在java中,创建引用的方法只要一种(new),且改变引用的方法也只有一种(赋值语句)。也就是说,程序员能对引用进行的操作只有创建和赋值。java的引用被称为安全指针

问:怎样才能让一个类不可变?
答:要保证一个可变类型的实例变量的数据类型的不可变性,需要得到一个本地副本,这被称为保护性复制,当这也不一定达到目的。得到副本是一方面,保证没有任何实例方法能够改变数据的值是另一方面。

1.3背包、队列和栈

泛型:

集合类的抽象数据类型的一个关键特性是我们可以用它们存储任意类型的数据。java有机制实现这一点,叫泛型,也叫作参数化类型。类型后的记号将Item定义为一个类型参数,他是一个象征性的占位符,表示用例将会使用的某种具体数据类型。

说的简单点,泛型就是一个占位的标志,目前不知道,在代码中动态指定的类型。这样有利于代码类的复用呀。

有了泛型,只需要实现一份API(和一次实现)就能够处理所有类型的数据,甚至是未来定义的数据类型。
创建泛型数组在java中不被允许。我们需要使用类型转换:

a = (Item[])new Object[cap];

例如:

class FixedStack<Item>{
    private Item[] a;//stack entries
    private int N;//size
    public FixedStack(int cap){
        a = (Item[]) new Object[cap];//创建数组
    }
}

背包
背包是一种不支持从中删除元素的集合数据类型–它的目的就是帮助用例收集元素并迭代遍历所有收集到的元素(用例也可以检查是否为空或者获取背包中元素的数量)。迭代的顺序不确定且与用例无关。

经典算法(算术表达式求值):
用两个栈(一个保存运算符,一个用于保存操作数)完成。具体实现:
1.将操作数压入操作数栈
2.将运算符压入运算符栈
3.忽略左括号
4.在遇到右括号时,弹出一个运算符,弹出所需数量的操作数,并将运算符和操作数的运算结果压入操作数栈。

public static double doCompute(String s){
        Stack<String> ops = new Stack<>();
        Stack<Double> vals = new Stack<>();
        for (int i = 0; i < s.length(); i++) {
            char s1 = s.charAt(i);
            String ss = s1+"";
            if(ss.equals("(")){

            }else if((ss.equals("+"))||(ss.equals("-"))||(ss.equals("*"))||(ss.equals("/"))){
                ops.push(ss);
            }else if((ss.equals(")"))){
                String op = ops.pop();
                double v = vals.pop();
                if(op.equals("+")) v = vals.pop()+v;
                else if(op.equals("-")) v = vals.pop()-v;
                else if(op.equals("*")) v = vals.pop()*v;
                else if(op.equals("/")) v = vals.pop()/v;
                vals.push(v);
            }else {
                vals.push(Double.parseDouble(ss));
            }
        }
        return vals.pop();
    }

实现一份API的第一步就是选择数据的表示方式

嵌套类(内部类)可以访问包含它的类的实例变量,这也是我们使用嵌套类的主要原因,即方便访问。

注意:利用数组来实现栈和队列时,队列要额外考虑head和tail的因素。

链表
链表是一种递归的数据结构,它或者为空(null),或者是指向一个结点(node)的引用,该结点含有一个泛型的元素和一个指向另一条链表的引用。

算法和数据结构是相辅相成的。算法的代码实现很简单时,往往数据结构就不简单。

在结构化存储数据集时,链表是数组的一种重要替代方式。

按照以下步骤识别目标并使用数据抽象解决问题:

1.定义API
2.根据特定的引用场景开发用例代码
3.描述一种数据结构(一组值的表示),并在API所对应的抽象数据类型的实现中根据它定义类的实例变量
4.描述算法(实现一组操作的方式),并根据它实现类中的实例方法
5.分析算法的性能特点

课后问答:
问:并不是所有语言支持泛型,有什么替代方案?
答:1.为每种类型的数据实现一个不同的集合数据类型2.构造一个Object对象的栈,并在用例中使用pop时间得到的对象转换为所需的数据类型。这种方法的缺点:类型不匹配错误只能在运行时发现

问:如何创建一个字符串栈的数组:
答:使用类型转换。

Stack<String>[] a = (Stack<String>[]) new Stack[n];

警告:这段类型转换跟上述Item的代码不同。你可能会认为需要使用Object而非Stack。在使用泛型时,java会在编译时检查类型的安全性,但会在运行时抛弃所有这些信息。因此在运行时语句右侧就变成了Stack[] 或者只剩下了Stack[] ,因此我们必须将他们转换为Stack[]。

需要栈时,请尽量不要使用java.util.Stack。他有很多额外的操作,尽管看起来有用,但实际上是累赘。同样的宽接口还有ArrayList和LinkedList。避免他们的原因是,这样无法高效实现所有方法。我们坚持窄接口的原因是:他们能够限制用例代码的行为,并使得用例代码更加易懂。

关于遍历:
返回一个迭代器。在我们的实现中,迭代器能够使用例能够迭代访问数据结构中的所有元素而无需知道链表的实现细节。要在集合数据类型中实现迭代,首先添加如下代码,这样我们才能够引入java的Iterator接口:

implements java.util.Iterator

第二步,在类的声明中添加这行代码,他保证了类必然会提供一个iterator()方法:

implements Iterable<Item>

iterator()方法本身只是简单的从实现了Iterator接口的类中返回一个对象:

public Iterator<Item> iterator(){return new ListIterator();}

实现了返回迭代器的Bag数据结构:

public class Bag<Item> implements Iterable<Item> {
    //链表实现Bag
    private Node first;//链表首结点
    private class Node{
        Item item;
        Node next;
    }
    //和stack的push方法完全相同,在头部添加
    public void add(Item item){
        Node oldFirst = first;
        first = new Node();
        first.item = item;
        first.next = oldFirst;
    }
    @Override
    public Iterator<Item> iterator() {
        return new ListIterator();
    }

    private class ListIterator implements Iterator<Item>{
        private Node current = first;

        @Override
        public boolean hasNext() {
            return current!=null;
        }

        @Override
        public Item next() {
            Item item = current.item;
            current = current.next;
            return item;
        }
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值