BAT经典面试题

实现一个Memcpy函数

memcpy函数的功能是从源内存地址的起始位置开始拷贝若干个字节到目标内存地址中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void *memcpy(void *dst, const void *src, size_t len)
{
if(NULL == dst || NULL == src){
return NULL;
}

void *ret = dst;

if(dst <= src || (char *)dst >= (char *)src + len){
//没有内存重叠,从低地址开始复制
while(len--){
*(char *)dst = *(char *)src;
dst = (char *)dst + 1;
src = (char *)src + 1;
}
}
else{
//有内存重叠,从高地址开始复制
src = (char *)src + len - 1;
dst = (char *)dst + len - 1;
while(len--){
*(char *)dst = *(char *)src;
dst = (char *)dst - 1;
src = (char *)src - 1;
}
}
return ret;
}

STL中vector的实现原理 (衍生:Map, Set等实现原理)

vector的数据安排以及操作方式,与array非常相似。两者的唯一区别在于空间的运用的灵活性。
array是静态空间,一旦配置了就不能改变;要换个大(或小)一点的房子,可以,一切琐细都得由客户端自己来:首先配置一块新空间,然后将元素从旧址一一搬往新址,再把原来的空间释还给系统。
vector是动态空间,随着元素的加入,它的内部机制会自行扩充空间以容纳新元素。因此,vector的运用对于内存的合理利用与运用的灵活性有很大的帮助,我们再也不必因为害怕空间不足而一开始要求一个大块头的array了,我们可以安心使用array,吃多少用多少。
vector的实现技术,关键在于其对大小的控制以及重新配置时的数据移动效率。一旦vector的旧有空间满载,如果客户端每新增一个元素,vector的内部只是扩充一个元素的空间,实为不智。因为所谓扩充空间(不论多大),一如稍早所说,是” 配置新空间/数据移动/释还旧空间 “的大工程,时间成本很高,应该加入某种未雨绸缪的考虑。稍后我们便可看到SGI vector的空间配置策略了。 另外,由于 vector维护的是一个连续线性空间,所以vector支持随机存取 。 注意:vector动态增加大小时,并不是在原空间之后持续新空间(因为无法保证原空间之后尚有可供配置的空间),而是以原大小的两倍另外配置一块较大的空间,然后将原内容拷贝过来,然后才开始在原内容之后构造新元素,并释放原空间。因此, 对vector的任何操作,一旦引起空间重新配置,指向原vector的所有迭代器就都失效了 。这是程序员易犯的一个错误,务需小心。

给定N张扑克牌和一个随机函数,设计一个洗牌算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void shuffle(int cards[],int n)
{
if(cards==NULL)
return ;

srand(time(0));

for(int i=0;i<n-1;++i)
{
//保证每次第i位的值不会涉及到第i位以前
int index=i+rand()%(n-i);
swap(cards[index], cards[i]); //交换位置,抽到了第i张牌,后面不会再用了,所以交换
}
}

假定N=54,首先,我们有一个随机函数发生器,能够产生1-54之间的随机数,如何保证抽第一张牌是54中可能,抽第二张牌是53中可能,……

可以这样做,假设扑克牌是一个54维的数组card, 我们要做的就是从这个数组中随机取一个元素,然后在剩下的元素里再随机取一个元素… 这里涉及到一个问题,就是每次取完元素后,我们就不会让这个元素参与下一次的选取。

我们要实现的目的是以等概率的方式将这54个数随机打乱排列,因此,可以这样处理:

第一次抽牌在初始54张牌中,将 随机产生的牌x,与第一个元素互换,

第二次抽牌在剩下的53张牌中,将 随机产生的牌y,与第二个元素互换,

……

25匹马,5个跑道,每个跑道最多能有1匹马进行比赛,最少比多少次能比出前3名?前5名?

参考链接