580 likes | 717 Views
第三章 向量. 基本概念. 向量 扩展数组 向量的应用:矩阵( matrix). 程序的灵活性. 例:编程实现一个读取 n 个 字符串类型 数据的程序( 参见 C3_1.java ) Public static void main(String args[]) { ReadStream r = new ReadStream(); String v1,v2,v3,v4; v1 = r.readString(); v2 = r.readString(); v3 = r.readString();
E N D
基本概念 • 向量 • 扩展数组 • 向量的应用:矩阵(matrix)
程序的灵活性 例:编程实现一个读取n个字符串类型数据的程序(参见C3_1.java) Public static void main(String args[]) { ReadStream r = new ReadStream(); String v1,v2,v3,v4; v1 = r.readString(); v2 = r.readString(); v3 = r.readString(); v4 = r.readString(); } 评注:要用n个字符串类型的变量去指向这n个字符串。
问题 • 略加改进(参见C3_2.java): • ReadStream r = new ReadStream(); • String data[]; • int n=4; • data=new String[n]; • for (int i=0;i<n;i++) • data[i]=r.readString(); • 评注:虽然通过数组建少了标识符的使用,但n需在编译时确定,程序仍然不够灵活。 • 能否有一种好的改进?如果n!= 4,n = 5,n= 6,n = 10000。该如何解决?
问题 • 极端做法:在编译前给n赋一个很大的值,如n = 1000000000。(参见C3_3.java) • 这样做可行吗? • int i=0; • int n=1000000000; • data=new String[n]; • for (r.skipWhite();!r.eof();r.skipWhite()) • { data[i]=r.readString(); • i++; • }
问题 • 进一步改进(参见C3_4.java): int n; n=r.readInt(); data=new String[n]; for (int i=0;i<n;i++) data[i]=r.readString(); • 要求用户在程序运行时确定n的大小。 • 把确定数组上限的负担转移给程序的用户身上。
问题 • 以上的实现存在的缺点: • 不灵活 • 浪费空间 • 负担转嫁到用户身上。 • 终极改进:我们需要一个灵活,不浪费空间,不增加用户负担的实现。这需要用到一种新的数据结构——向量(vector)。
向量 读取字符串的程序: public static void main(String args[]) { ReadStream r = new ReadStream(); Vector data; data = new Vector(); for( r.skipWhite();!r.eof();r.skipwhite()){ String s = r.readString(); data.add(s); } 程序并没有显示的跟踪data中存储的字符串的个数。
向量和数组初步对比 • 相似点: • 语义相似(new创建),向量的底层是数组。 • 都可以随机访问(可以按任意顺序访问)。 • 不同点: • 数组是静态的,而向量是动态的。 • 向量可以缩减增加,而数组不可以。
向量的接口(部分) • 从抽象列表继承而来;Vector extends AbstractList • 无参构造方法Vector(),默认向量的容量为10; • 含参构造方法Vector(int initialCapacity) • 在向量的末尾添加一个元素void add(Object obj) • 删除一个元素Object remove(Object element) • 查询向量的第?号元素Object get(int index) • 在向量的第?号位置插入一个元素void add(int index, Object obj) • 检查向量是否为空?boolean isEmpty() • 删除向量的第?号元素Object remove(int where) • 替换向量的第?号元素值Object set(int index, Object obj) • 向量的大小。int size()
字母频率 • 替代型密码的破译,字母频率 明文: how are you dong 替代加密 解密 密文: ipx bsf zpv epoh 字母p的频率为23.1%(3/13) • 统计学家对大量的文档统计后发现:每个字母在文档中出现的频率是一定的。 • 假设统计学家告诉我们:字母o的出现频率为23.1%。
字频率word-frequency • 作者的写作风格,单词的频率
设计思想 字符流r是否结束? N 读入单词 在字频向量中有记录否? Y 把新单词加入 字频向量, 频率置为1 N 逐个打印向量元素 Y 该单词频率加 1
向量的每一个元素类型都是Association (String,Integer) theKey theValue null null 1 null null 2 … … ... null null 999 null null 1000 vocab向量, 大小=0, 容量=1000 算法描述 • Vector vocab=new Vector(1000); • 字符流r:long long ago
向量的每一个元素类型都是Association (String,Integer) 向量的每一个元素类型都是Association (String,Integer) theKey theValue theKey theValue “long” 1 1 null null 1 null null 2 null null 2 … … ... … … ... null null 999 null null 999 null null 1000 null null 1000 vocab向量, 大小=1, 容量=1000 vocab向量, 大小=0, 容量=1000 字符流r:long long ago 1.读入第1个“long”
向量的每一个元素类型都是Association (String,Integer) 向量的每一个元素类型都是Association (String,Integer) theKey theKey theValue theValue “long” “long” 2 1 1 1 null null null null 2 2 … … … … ... ... null null null null 999 999 null null null null 1000 1000 vocab向量, 大小=1, 容量=1000 vocab向量, 大小=1, 容量=1000 字符流r:long long ago 2. 读入第2个“long”
向量的每一个元素类型都是Association (String,Integer) 向量的每一个元素类型都是Association (String,Integer) theKey theKey theValue theValue “long” “long” 2 2 1 1 “ago” null 1 null 2 2 … … … … ... ... null null null null 999 999 null null null null 1000 1000 vocab向量, 大小=1, 容量=1000 vocab向量, 大小=2, 容量=1000 字符流r:long long ago 3. 读入 “ago”
for( r.skipwhite() ; !r.eof() ; r.skipWhite() ){ Association wordInfo; String vocabWord; String word = r.readString(); for( i=0; i < vocab.size(); i++) { wordInfo = (Association)vocab.get(i); vocabWord = (String)wordInfo.getKey(); if( vocabWord.equals(word) ){ Integer f = (Integer)wordInfo.getValue(); wordInfo.setValue(new Integer(f.intValue()+1)); break; } } if( i== vacab.size() ) vocab.add(new Association(word , new Integer(1))); }
向量类的实现 • 向量必须存储大量相似的记录,因此有一个Object类型的数组。 Object可以是字符串String(P30)、关联Association(P33)或者另外的向量Vector(P44)等。 • 向量应有一个描述当前向量大小(size())的整型数。 • 当向量的大小将超过容量(底层数组的长度:数组名.length),向量的容量就会增加。 • protected Object elementData[ ]; • protected int elementcount;
0 1 2 3 4 5 6 elementCount=0 向量的构造方法 public Vector() // post: constructs a Vector with capacity for 10 elements { this(10); // call one-parameter constructor } public Vector(int initialCapacity) { Assert.pre(initialCapacity >= 0,"Nonnegative capacity."); elementData = new Object[initialCapacity]; elementCount = 0; }
0 1 2 3 4 5 6 1 6 3 7 Index=1 elementCount=4 访问向量元素 *@pre0 <= index && index < size() *@post returns the element stored in location index public Object get(int index) { return elementData[index]; }
0 1 2 3 4 5 6 1 6 3 7 Index=1 elementCount=4 修改向量元素 *@pre 0 <= index && index < size() *@post element value is changed to obj; old value is returned public Object set(int index, Object obj) { Object previous = elementData[index]; elementData[index] = obj; return previous; }
0 1 2 3 4 5 6 1 6 3 7 4 5 9 0 1 2 3 4 5 6 1 6 3 7 elementCount=7 Index=1 elementCount=4 向量中增加元素 • 向量的尾部添加一个元素 • 向量的中间需要添加一个元素
0 1 2 3 4 5 6 1 6 3 7 obj elementCount=4 1、向量尾部添加元素 • 需要考虑的问题: • 向量的容量问题,如果向量的容量不够,就需扩展 @post adds new element to end of possibly extended vector public void add(Object obj) { ensureCapacity(elementCount+1); elementData[elementCount] = obj; elementCount++; } elementCount=5
0 1 2 3 4 5 6 7 9 2、向量中间增加元素 • 使用双参数add(int index, Object obj)方法,需要移动元素来产生一个空位,实现插入。例如:排队。 最初状态:
0 0 0 0 0 0 1 1 1 1 1 1 2 2 2 2 2 2 3 3 3 3 3 3 4 4 4 9 4 4 5 5 4 4 5 5 5 5 6 5 6 6 6 6 6 7 6 7 7 7 7 7 7 9 最初状态: 第1次后移: 第2次后移: 第3次后移: 第4次后移: 放置元素9:
思考 • 向量在中间插入元素时能否正向移动?会出现什么情况?
0 0 0 0 0 0 1 1 1 1 1 1 2 2 2 2 2 2 3 3 3 3 3 3 4 4 4 9 4 4 4 4 4 5 4 4 4 6 4 4 4 6 7 4 4 7 7 4 4 4 错误的做法: 9 最初状态: 第1次后移: 第2次后移: 第3次后移: 第4次后移: 放置元素9:
中间增加元素实现 public void add(int index, Object obj) { int i; ensureCapacity(elementCount+1); for (i = elementCount; i > index; i--) { elementData[i] = elementData[i-1]; } elementData[index] = obj; elementCount++; }
删除一个元素 • Object remove(int where) • 在指定位置上删除一个元素。 • 该元素其后得元素必须向前移动一位 • 向量中元素的个数减少一个 • 需返回where位置上的值。
0 0 0 0 0 1 1 1 1 1 2 2 2 2 2 3 3 3 3 3 5 4 5 5 5 6 5 5 6 6 7 6 7 6 6 7 7 7 7 最初状态: 第1次移动: 第2次移动: 第3次移动: 最后状态:
删除元素的实现 @pre 0 <= where && where < size() @post indicated element is removed, size decreases by 1 @param where The location of the element to be removed. public Object remove(int where) { Object result = get(where); elementCount--; while (where < elementCount) { elementData[where] = elementData[where+1]; where++; } elementData[elementCount] = null; // free reference return result; }
删除元素的另一种实现 public int remove( Object elem) { int nIndex; for( int i= 0 ; i< elementcount; i++){ if( elem.equals(elementData[i]){ remove(i); nIndex = i; } } return nIndex; }
判断向量是否为空、求大小 public int size() { return elementCount; } public boolean isEmpty() { return size() == 0; }
可扩展性 • 对于向量,我们仍然需要估计其初始大小。 public Vector(int initialCapacity) { Assert.pre(initialCapacity >= 0,"Nonnegative capacity."); elementData = new Object[initialCapacity]; elementCount = 0; capacityIncrement = 0; initialValue = null; } • 随着向量中元素的增长,向量空间可能不够,怎么办?
扩展方法1 • 每次增加一个元素的空间,并将原先的向量元素复制到新的向量中。 • 定量分析:假设:向量初始空间为1,每一次空间用尽,向量的空间加1,将原向量元素复制到新的向量中。求:当向量元素个数为n时,复制元素的操作进行了多少次?
复制的个数 扩容前 扩容后 1 0 0 2 0 1 0 1 3 0 1 2 0 1 2 4 0 1 2 3 0 1 2 3
原向量 新向量 复制的个数 1 2 1 2 3 2 … … … n-2 n-1 n-2 n-1 n n-1 C = 1 + 2 + … + n-1 = n(n-1)/2
假设:向量初始空间为0,每一次空间用尽,向量的空间加k,将原向量元素复制到新的向量中。求:当向量元素个数为n时,复制元素的操作进行了多少次?假设:向量初始空间为0,每一次空间用尽,向量的空间加k,将原向量元素复制到新的向量中。求:当向量元素个数为n时,复制元素的操作进行了多少次? • 分析:为了简单起见,在不影响结果的前提下,我们设:
复制的个数 扩容前 扩容后 0 2 0 1 0 1 4 0 1 2 3 0 1 2 3 K=2
原向量 新向量 复制的个数 0 k 0 k 2k k 2k 3k 2k … … … (s-2)k (s-1)k (s-2)k (s-1)k s*k =n (s-1)k C = 0 + k + 2k + … + (s-1)k = ks*(s-1)/2= n(n/k -1)/2=
扩展方法2 • 如果每次当原向量空间用尽时,扩展的新向量是原向量的2倍,则求:当向量元素个数为n时,复制元素的操作进行了多少次? • 设:k = log2n,即:
原向量 新向量 复制的个数 0 1 0 1 2 1 2 4 2 … … … 2k=n C = 0 + 1 + 2 + …+2k-1=2k-1=n -1
比较 • C1 = n(n-1)/2 ; C2 = n – 1 哪个更好?
向量扩展的实现 public void ensureCapacity(int minCapacity) { if (elementData.length < minCapacity) { int newLength = elementData.length; if (capacityIncrement == 0) { if (newLength == 0) newLength = 1; while (newLength < minCapacity) newLength *= 2; } else { while (newLength < minCapacity) newLength += capacityIncrement; } Object newElementData[] = new Object[newLength]; int i; for (i = 0; i < elementCount; i++) newElementData[i] = elementData[i]; elementData = newElementData; } }
L系统 • 包含一个从字母表中得到的符号种子(又称起始字符串) • 一组改变或重写字符串的规则,称为生产 • L系统可以模拟简单生物体的生长过程
0 0 0 0 1 1 1 1 2 2 2 2 3 3 3 3 4 4 4 4 5 5 5 5 6 6 6 6 7 7 7 7 8 8 8 8 9 9 9 9 S L S L 最初状态 第0次 第1次 第2次
0 0 0 1 1 1 2 2 2 3 3 3 4 4 4 5 5 5 6 6 6 7 7 7 8 8 8 9 9 9 L S L S S L L L L S S L L L S L 0 1 2 3 4 5 6 7 8 9 10 11 12 S L L S L L S L S L L S L 第3次 第4次 第5次 第6次
实现(LSystem.java) public static Vector rewrite(Vector s) { Vector result = new Vector(); for (int pos = 0; pos < s.size(); pos++) { if (S == s.get(pos)) result.add(L); else if (L == s.get(pos)) { result.add(S); result.add(L); } } return result; }