1. 对称矩阵,因此压缩存储可以认为是只要存储下三角矩阵。
2. 广义表中的元素或者是一个不可分割的原子,或者是一个非空的广义表。(错误。广义表的元素可以为空。)
3. 既希望较快查找又便于线性表动态变化的查找方法是(C)
A 顺序查找 B折半查找 C 索引查找 D 哈希查找
分析:索引查找是在索引表和主表(即线性表的索引存储结构)上进行的查找。索引查找的过程是:首先根据给定的索引值K1,在索引表上查找出索引值等于K1的索引项,以确定K1对应的子表在主表中的开始位置和长度,然后再根据给定的关键字K2,在对应的子表中查找出关键字等于K2的元素(结点)。
对索引表或子表进行查找时,若表是顺序存储的有序表,则既可进行顺序查找,也可进行二分查找。否则只能进行顺序查找。
索引顺序查找又称为分块查找,是介于顺序查找和二分查找之间的一种查找方法。
分析二:希望较快而不是很快,并且希望便于动态变化,这个用C而不是D,如果哈希法的存储不是链式,一般的情况下随着关键字的增多,冲突频繁发生,查找性能会急剧下降,其实并不是太利于动态变化,索引顺序由于一般块内可以无序,因此块内可以方便地减少增加。
4. 分析下列程序,哪个不可以表示a[1]地址:A
#include<iostream>
#include<string.h>
using namespace std;
int main() {
int a[5]={1,2,3,4,5};
cout<<a+sizeof(int)<<endl; // 不正确, 在32位机器上相当于指针运算 a + 4
cout<<&a+1<<endl; // 正确,数组首元素地址加1,根据指针运算就是a[1]的地址
cout<<(int *)&a+1<<endl; // 正确,数组地址被强制类型转换为int*,然后加1,这样和B表示的一个意思
cout<<(int*)((char*)&a+sizeof(int)); // 正确,数据地址先被转换为char*,然后加4,根据指针运算公式,向前移动4 * sizeof(char),之后被转换 //为int*,显然是a[1]的地址
return 0;
}
A a+sizeof(int) B &a+1 C (int *)&a+1 D (int*)((char*)&a+sizeof(int))
5. Name -> ID 就是 string -> int,字符串最好用 Hash 或 Tree 来索引,不过由于 Name 不是唯一的,一个 Name 可能对应多个 ID,要用 Linked List 做对应过来的结构。所以可以是 Hash + Linked List 或 Tree + Linked List。(Tree 可以用 Trie,复杂度是 O(length(name)),其实 Hash 会更好,理论上复杂度是 O(1))。
ID -> Name 就是 int(7 digit) -> string,7 位数也就是 1000 w,可以开得下(人家机子好),所以就可以随机查了。
6. 1).矩阵的下三角形i>=j ;a[i][j]是第1+2+...+i-1+j=(i-1)*i/2+j个元素应该选A
2).矩阵的上三角形i<=j ;第一列存了1个,第二列存了2个,第j-1列存了j-1个,最后一列存了i个。因此,a[i][j]是第1+2+...+j-1+i=(j-1)*j/2+i个元素。
7.用数组r存储静态链表,结点的next域指向后继,工作指针j指向链中结点,使j沿链移动的操作为(A)
A j=r[j].next
分析:注意,静态链表,所以j是数组的下标。
用数组r存储静态链表,结点的next域指向后继。实际上是说,节点的next才是下一个元素,节点不是链中元素,节点的next才是后继。
分析二:首先链表的下一个结点应该用next指针访问,排除B;
当前结点是r[j],可以排除C;
r[j]是当前节点而不是指向当前节点的指针,因此用r[j].next;
8.若要删除book表中的所有数据,以下哪些语法是错误的(AD)
A drop table book Btruncate table book Cdelete from book Ddelete *from book
分析:A: drop table book 是删除整个表,题目的潜在意思是删除表中的数据而并非删除整个表。因此A错。
B: truncate table book 是删除表中的数据,删除速度比delete更快,无法撤回(回退)。
C: delete from book 删除数据表中的数据,可以回退,可添加where 子句。
D:语法错误
9.当用一个大小为6的数组来实现循环队列,且用rear和front的值分别为0和3.当从队列中删除一个元素,再加入两个元素后,rear和front分别为: 2 和 4.
分析:1)、队列添加元素是在对尾,删除元素是在对头;
2)、添加元素,尾指针rear+1;删除元素,头指针front+1;
3)、本题中,删除一个元素,front+1,也就是3+1=4;添加2个元素,rear+2,也就是0+2=2;
10.对于class java.util.ArrayList:
元素在集合中有序,指的是元素插入过程中记录了元素的插入顺序。
ArrayList就是动态数组,用MSDN中的说法,就是Array的复杂版本,它提供了如下一些好处:
1> 动态的增加和减少元素
2> 实现了ICollection和IList接口
3> 灵活的设置数组的大小
Synchronized属性指示当前的ArrayList实例是否支持线程同步,而ArrayList.Synchronized静态方法则会返回一个ArrayList的线程同步的封装。
如果使用非线程同步的实例,那么在多线程访问的时候,需要自己手动调用lock来保持线程同步.
1).数组大小指定之后是不可变的,集合是可变的;
2).List中元素可以重复,Set不可重复;
3).ArrayList的存储结构是线性的,动态分配内存,有序的,可以重复,优点是可以随机访问快;
4).ArrayList是线程不安全的,不需要线程同步;
ArrayList和Vector是类似的:
(1)两者都是基于索引的,内部有一个数组支持
(2)两者都维护插入的顺序
(3)两者都允许null值
不同之处在于:
(1)ArrayList是不同步的,而Vector是同步的,所以ArrayList更高效,不会过载
(2)ArrayList更加通用,因为我们可以使用Collections工具类轻易地获取同步列表和只读列表,当需要在迭代的时候对列表进行改变,你应该使用CopyOnWriteArrayList
11.对长度为n的线性表进行顺序查找,在最坏情况下所需要的比较次数为:n
分析:最坏情况就是需要查找的数在最后一个或者不存在,那么比较次数就是n。
12. 若在内存中,则数据可以”随机存取”,但内存数据被读取或写入时,所需要的时间与这段信息所在的位置无关.但是在读取和写入磁盘时,其所需要的时间与位置就会有关系.因为在BASIC,PASCAL和C/C++语言中,数组的存放是按照行优先来存放的,按行号第一行第二行…以此类推.本体关键是考察内存抖动的问题,如果按列访问则需要跳过一大串内存地址,这样可能需求的内存地址不在当前页中则需要进行页置换,这样便需要硬盘IO,减低速度。
当数组过大的时候CPU无法一次性读取整个内存页面,这时候访问数据的时候就会发生页面置换,如果以列序来访问就会发生频繁的置换,这时候就会消耗大量的时间。(跨列访问虚存可能发生页分裂,页表频繁切换导致效率降低)
如果数组很大的话应该是行优先快,因为数组在内存中是按行优先存储的,在虚存环境下,如果整个数组没有在内存中的话可以比列优先减少内存换进换出的次数。就算整个数组都在内存中,列优先访问a[i][j]还得计算一次乘法,行优先只需加一就可以了,这个可以忽略。
13. 数组指针(也称行指针)
定义 int (*p)[n];
()优先级高,首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长。也就是说执行p+1时,p要跨过n个整型数据的长度。
如要将二维数组赋给一指针,应这样赋值:
int a[3][4];
int (*p)[4]; //该语句是定义一个数组指针,指向含4个元素的一维数组。
p=a;//将该二维数组的首地址赋给p,也就是a[0]或&a[0][0]
p++;//该语句执行过后,也就是p=p+1;p跨过行a[0][]指向了行a[1][]。
所以数组指针也称指向一维数组的指针,亦称行指针。
指针数组
定义 int *p[n];
[]优先级高,先与p结合成为一个数组,再由int*说明这是一个整型指针数组,它有n个指针类型的数组元素。这里执行p+1是错误的,这样赋值也是错误的:p=a;因为p是个不可知的表示,只存在p[0]、p[1]、p[2]...p[n-1],而且它们分别是指针变量可以用来存放变量地址。但可以这样*p=a; 这里*p表示指针数组第一个元素的值,a的首地址的值。
如要将二维数组赋给一指针数组:
int *p[3];
int a[3][4];
for(i=0;i<3;i++)
p[i]=a[i];
这里int *p[3] 表示一个一维数组内存放着三个指针变量,分别是p[0]、p[1]、p[2]
所以要分别赋值。
这样两者的区别就豁然开朗了,数组指针只是一个指针变量,似乎是C语言里专门用来指向二维数组的,它占有内存中一个指针的存储空间。指针数组是多个指针变量,以数组形式存在内存当中,占有多个指针的存储空间。
还需要说明的一点就是,同时用来指向二维数组时,其引用和用数组名引用都是一样的。
比如要表示数组中i行j列一个元素:
*(p[i]+j)、*(*(p+i)+j)、(*(p+i))[j]、p[i][j]。
14. 在java 中,声明一个数组时,不能直接限定数组长度,只有在创建实例化对象时,才能对给定数组长度.。
如下,1,2,3可以通过编译,4,5不行。而String是Object的子类,所以上述BCF均可定义一个存放50个String类型对象的数组。
1. String a[]=new String[50];
2. String b[];
3. char c[];
4. String d[50];
5. char e[50];
A:char[][] 定义了二位字符数组。在Java中,使用字符串对char数组赋值,必须使用toCharArray()方法进行转换。所以A错误。
B、C:在Java中定义String数组,有两种定义方式:String a[]和String[] a。所以B、C正确。
D、E:数组是一个引用类型变量,因此使用它定义一个变量时,仅仅定义了一个变量 ,这个引用变量还未指向任何有效的内存 ,因此定义数组不能指定数组的长度。所以D、E错误。
F:Object类是所有类的父类。子类其实是一种特殊的父类,因此子类对象可以直接赋值给父类引用变量,无须强制转换,这也被称为向上转型。这体现了多态的思想。所以F正确。
15.下列说法正确的是(ABD)
A 二维以上的数组是一种特殊的广义表
B 数组一旦建立,结构的元素个数和元素间的关系就不再变
C 数组是一种线性结构,因此只能用来存储线性表
D 数组采用顺序存储方式表示
分析:需要区分数据的物理结构与逻辑结构。物理结构主要是指存储方式,包含线性存储与链式存储,它是从计算机存储的角度去考虑。逻辑结构指的是数据之间的关系,有线性关系和链式关系等,主要是从人为定义角度去考虑。数组是一种被人们定义为线性关系的表,至于其存储结构,可以用线性存储也可以是链式存储去存储。
16.顺序存储结构的存储一定是连续的;链式存储结构的存储空间不一定是连续的,可以是连续的。
链式存储结构中每个结点都由数据域与指针域两部分组成,增加了存储空间。
17.给定一个m行n列的整数矩阵,每行从左到右和每列从上到下都是有序的。判断一个整数k是否在矩阵中出现的最优算法,在最坏情况下的时间复杂度为:o(m+n)
分析:首先,按题中要求所得矩阵的左上角和右下角元素分别为整个矩阵的最小值和最大值,这俩个点是矩阵的鞍点。
下面是最优算法:
记矩阵的右上角(左下角也可以)元素为a,搜索起点设置为a,要查找的元素为k:
若a>k,则a所在列的所有元素均大于k,搜索位置左移1位,然后删除该列构成新的矩阵;
若a<k,则a所在行的所有元素均小于k,搜索位置下移1位,然后删除该行构成新的矩阵;
若相等,结束查找;
由新构成的矩阵利用上述方式继续查找(递归调用)。
本题中,该最优算法的最坏情况也就是说从右上角开始搜索直到左下角结束,每次向左或向下一步,共需要m+n步到达左下角。
18.在顺序表中访问任意一个结点的时间复杂度均为O(1),因此顺序表也称为随机存取的数据结构。