第一部分(必做): 计算机科学基础
1. (单选)软件设计中模块划分应该遵循的准则是:
A.低内聚低耦合 B.高内聚低耦合 C.低内聚高耦合 D.高内聚高耦合
答:B
内聚指模块内部各成分之间相关程度的度量 强度性低到高分成 偶然内聚 :关系松散没什么联系 逻辑内聚:几个逻辑上相关的功能被放在同一模块中,如一个模块读取各种不同类型外设的输入,逻辑内聚的模块各成分在功能上并无关系。时间内聚:一个模块完成的功能必须在同一时间内执行,这些功能只是因为时间因素关联在一起。通信内聚:如果一个模块的所有成分都操作同一数据集或生成同一数据集,则称为通信内聚。顺序内聚: 如果一个模块的各个成分和同一个功能密切相关,而且一个成分的输出作为另一个成分的输入,则称为顺序内聚。功能内聚:模块的所有成分对于完成单一的功能都是必须的,则称为功能内聚。信息内聚:模块完成多个功能,各个功能都在同一数据结构上操作,每一项功能有一个唯一的入口点。这个模块将根据不同的要求,确定该模块执行哪一个功能。
耦合指模块之间的关联程度。耦合由高到低分成:内容耦合:当一个模块直接修改或操作另一个模块的数据时,或一个模块不通过正常入口而转入另一个模块时,这样的耦合被称为内容耦合。公共耦合:两个或两个以上的模块共同引用一个全局数据项,这种耦合被称为公共耦合。外部耦合:一组模块都访问同一全局简单变量而不是同一全局数据结构。控制耦合:一个模块通过接口向一个模块传递控制信号,接受信号的的模块根据信号值进行适当的动作。标记耦合:一个模块通过接口向另一个模块传递一个控制信号,接受信号的模块根据信号值而进行适当的动作,这种耦合被称为控制耦合。数据耦合:模块之间通过参数传递数据。非直接耦合:两个模块间没有直接关系,完全通过主模块的控制和调用实现。
2. (单选)最坏情况下时间复杂度不是n(n-1)/2的排序算法是:
A.快速排序 B.冒泡排序 C.直接插入排序 D.堆排序
答:D
快排最差是每次partion得到的位置在数组的两端时出现如排序有序数组。
每次partion全部比较一次,每一趟比较后一个数位置确定,下次比较少一个数字少比较一次
(1+…N-1)
冒泡每一趟通过相邻数字间交换实现把最大或最小数排到数组两端
比较次数为(1+…N-1)
直接插入最差情况是在逆序时如从小到大排序时每个数都比前面的所有数小
比较次数(1+..N-1)
堆排序最好最差情况一样 都为nlogn
3. 哈希表中解决冲突的方法通常可以分为open addressing和chaining两类, 请分别解释这两类冲突解决方法的大致实现原理
答:第一个使用冲突算法在哈希表中在此寻找合适位置,
分为线性探测再散列,存储地址D发生冲突,则放到存储地址(D+1)%m;若又发生冲突则放到存储地址(D+2)%m二次探测,ND = (D+di)%m; di取1*1,-1*1,2*2,-2*2,……,K*K,-K*K (K≤m/2)。拉链法个将所有关键字为同义词的结点链接在同一个单链表中。。
一 开散列法--------chaining
又叫做拉链法。即有多个元素hash到同一位置,就拉出一个链表储存。这样做最坏情况变成了O(n)。但是只要hash函数选取得当,拉链法都能得到相当优秀的效率
二 闭散列法--------open addressing
又叫做开放寻址法。这种方法不需另外建链表,而是所有元素都存在散列表里。插入时当一个元素对应位置已满,它就寻找下一个空位置储存。亦即,一个元素可能不在它应在的地方。这时,如何寻找下一个位置就非常重要。主要有三种方法:
1 线性探查。即当第i个位置已经有了元素,就考虑第i+1个位置。即,探测的位置关于探测次数是线性函数。线性探查随着插入的增多,会出现连续被占用的位置,使得平均查找时间不断增加,称为一次群集。线性探查效率比较低下。
2 二次探查。二次探查每次并不直接探查下一个元素,而是增加一个偏移量,使得探测的位置关于探测次数是二次函数。二次探查比线性探查有所改进,但是对于hash函数的选取限制较大。而且,初始位置相同的元素探查的位置也会完全相同,这就导致了比一次群集程度轻的二次群集。因此二次探查效率也并不理想。
3 双重散列。即构造两个hash函数。当发生冲突时,向下寻找hash2(k)的位置。这样,探查的序列就与元素本身的值有关,发生冲突的概率小了很多。而hash2的选取也有限制:hash2(k)要始终与整个表的大小互质,才能取遍所有位置。
实际测试中,拉链法速度最快,双重散列稍慢。而线性探查和二次探查速度较为低下。
4. 简单的链表结构拥有很好的插入 删除节点性能, 但随机定位(获取链表第n个节点)操作性能不佳, 请你设计一种改进型的链表结构优化随机定位操作的性能, 给出设计思路及其改进后随机定位操作的时间复杂度
答:使用 Node * List[MAX]按顺序存储链表节点地址,随机访问时间复杂度O(1);相对的删除增加节点时间变成O(n);
大概地说,节点构成多棵相连的完全二叉树来表示(为了不浪费节点),存取顺序为前序遍历。
复杂度为O( log n )
这里有代码
http://www.cs.oberlin.edu/~jwalker/ra-list/
5. 什么是NP问题?列举典型的NP问题(至少两个)?对于一个给定的问题你通常如何判断它是否为NP问题?
答:NP问题是可以在多项式时间内被确定机(通常意义的计算机)解决的问题.NP(Non-Deterministic Polynomial, 非确定多项式)问题,是指可以在多项式时间内被非确定机(它可以猜,他总是能猜到最能满足你需要的那种选择,如果你让他解决n皇后问题,他只要猜n次就能完成----每次都是那么幸运)解决的问题。经典问题有:旅行商问题 TSP Travelling Salesman Problem、子集和问题、Hamilton回路、最大团问题
判断方法:可以将时间的时间复杂度与某个NP问题的时间复杂度作比较。。。
P(Polynomial,多项式)问题.P问题是可以在多项式时间内被确定机(通常意义的计算机)解决的问题.
NP(Non-Deterministic Polynomial, 非确定多项式)问题,是指可以在多项式时间内被非确定机(它可以猜,他总是能猜到最能满足你需要的那种选择,如果你让他解决n皇后问题,他只要猜n次就能完成----每次都是那么幸运)解决的问题.这里有一个著名的问题----千禧难题之首,是说P问题是否等于NP问题,也即是否所有在非确定机上多项式可解的问题都能在确定机上用多项式时间求解.
NPC(NP Complete)问题(NP完全),可以这么认为,这种问题只有把解域里面的所有可能都穷举了之后才能得出答案,这样的问题是NP里面最难的。
1.旅行商问题 TSP Travelling Salesman Problem(有一个推销员,要到n个城市推销商品,他要找出一个包含所有n个城市的具有最短路程的环路。)
2.子集和问题
3.Hamilton回路
要满足两个条件:
1.封闭的环
2.是一个连通图,且图中任意两点可达
经过图(有向图或无向图)中所有顶点一次且仅一次的通路称为哈密顿通路。
经过图中所有顶点一次且仅一次的回路称为哈密顿回路。
4.最大团问题
给定无向图G=(V, E),其中V是非空集合,称为顶点集;E是V中元素构成的无序二元组的集合,称为边集,无向图中的边均是顶点的无序对,无序对常用圆括号“( )”表示。如果U∈V,且对任意两个顶点u,v∈U有(u, v)∈E,则称U是G的完全子图。G的完全子图U是G的团当且仅当U不包含在G的更大的完全子图中。G的最大团是指G中所含顶点数最多的团。
6. 以下是一个tree的遍历算法, queue是FIFO队列, 请参考下面的tree, 选择正确的输出.
1
/ \
2 3
/ \ / \
4 5 6 7
queue.push(tree.root) while(true){ node=queue.pop(); output(node.value);//输出节点对应数字 if(null==node) break; for(child_node in node.children){ queue.push(child_node); } }
A. 1234567
B. 1245367
C. 1376254
D. 1327654
分析:貌似是层次遍历,使用队列
第二部分(选作): C/C++程序设计
1. 有三个类A B C定义如下, 请确定sizeof(A) sizeof(B) sizeof(C)的大小顺序, 并给出理由
struct A{ A() {} ~A() {} int m1; int m2; }; struct B{ B() {} ~B() {} int m1; char m2; static char m3; }; struct C{ C() {} virtual~C() {} int m1; short m2; };
分析:8 8 12
A:8字节变量
B:8字节 char因为4字节对齐占4个字节,static不存储在类中
C:四字节对齐变量一共占8字节,有虚函数加4字节虚指针一共12字节。
2. 请用C++实现以下print函数,打印链表I中的所有元素, 每个元素单独成一行
void print(const std::list<int> &I){
}
void print(const std::list<int> &I){ for(const std::list<int>::const_itorator it = I.begin():it != I.end(): it++)//访问常量必须使用常迭代器。 cout<<*it<<endl; }
3. 假设某C工程包含a.c和b.c两个文件,在a.c中定义了一个全局变量foo, 在b.c中想访问这一变量时该怎么做?
答:使用extern
4. C++中的new操作符通常完成两个工作, 分配内存及其调用相应的构造函数初始化
请问:
1) 如何让new操作符不分配内存, 只调用构造函数?
2) 这样的用法有什么用?
使用定位放置new #include <new> // 必须 #include 这个,才能使用 "placement new" #include "Fred.h" // class Fred 的声明 void someCode() { char memory[sizeof(Fred)]; // Line #1 void* place = memory; // Line #2 Fred* f = new(place) Fred(); // Line #3 (详见以下的“危险”) // The pointers f and place will be equal // ... } 作用为:对于需要反复创建并删除的对象,可以降低分配释放内存的性能消耗
答:参考 http://blog.csdn.net/aixiaolin/article/details/7367237
5. 下面这段程序的输出是什么?为什么?
class A{ public: A(){p();} virtual void p(){print("A")} virtual ~A(){p();} }; class B{ public: B(){p();} void p(){print("B")} ~B(){p();} }; int main(int, char**){ A* a=new B(); delete a; }
输出:ABBA
原因:先构造父类 再构造子类 子类构造完成前virtual无效,析构虚函数会先析构子类
6. 什么是C++ Traits? 并举例说明
答:特性萃取 template <class T> class Demol{ typedef T Type; } template <class T>//偏特化 class Demol<T *>{ typedef T Type; }