2013/08/13,秋季校招准备工作第二天。
3. 打乱数组
题目:
将一个长度为n的数组随机打乱。
解答:
思路:将数组内的每个元素与它所在位置到结尾之间的一个随机位置的元素交换:
void Shuffle(int *data, int n)
{
int i;
for (i = 0; i < n; ++i)
Swap(&data[i], &data[rand()%(n-i) + i]);
}
4. 不重复的随机数(题目003)
题目:
输入正整数m、n,向指定数组中填充m个[0,n-1]的不重复的随机数。如果m>n,只输出n个随机数。
解答:
方法1:将已经产生的随机数保存在一个列表中,以后每产生一个随机数就在列表中查找是否重复,如果重复则重新产生随机数。
缺点:最坏情况下运行时间很长,在m/n较大的时候难以估计这个时间。
优点:代码简单,方法直观,适合m/n很小的情况。
方法2:产生一个长度为n的临时数组temp,且temp[i]=i,然后将temp打乱,取前m个填到指定数组中。
void RandomFill(int *dest, int n, int m)
{
int i;
int *temp = malloc(n * sizeof(int));
for (i = 0; i < n; ++i)
temp[i] = i;
Shuffle(temp, n);
memcpy(dest, temp, max(m, n) * sizeof(int));
free(temp);
}
缺点:空间复杂度为O(n),时间复杂度为O(m+n),在m/n较小时开销较大。
优点:在m/n较大时能保证线性时间内运行完毕。
方法3: 0到n-1这n个数,每个数被选中的概率都是m/n,而rand()%n<m的概率正好是m/n,利用这个性质,我们得到下面的代码:
void RandomFill(int *dest, int n, int m)
{
int i, j = 0;
for (i = n-1; i >= 0; --i, --n)
if (rand()%n < m) {
dest[j++] = i;
--m;
}
Shuffle(dest, j);
}
循环中的m是还要挑m个数,n是有n个数可以挑,如果数i被挑中,则m需要减1,而无论i有没有被挑中,可挑数的个数都需要减1。
缺点:方法不太直观。
优点:空间复杂度O(1),时间复杂度O(n)。性能在三种方法中最好。
5. 利用库函数排序(题目005)
题目:
有结构体Athlete:struct Athlete{int speed;char name[20];},对此结构体按speed和name项分别进行排序。要求利用库函数qsort。
解答:
要使用qsort函数先要引入头文件stdlib.h,它的原型为:
void qsort(void* base, size_t num, size_t size, int (*cmp)(const void*, const void*));
重点是第四个参数,即自定义的排序规则,对于此题,我们需要两个函数作为不同的排序规则:
int CmpSpeed(const void *a,const void *b)
{
return ((const Athlete*)b)->speed-((const Athlete*)a)->speed;
}
int CmpName(const void *a,const void *b)
{
return strcmp(((const Athlete*)a)->name,((const Athlete*)b)->name);
}
qsort规定了作为排序规则的函数的参数,因此我们需要在实际处理前将void*参数转换为它实际类型的指针,然后再根据自己的需要返回一个值,如果返回值为负数,则a<b,如果返回值为0,则a=b。
排序主函数为:
Athlete list[]={{100, "Tom"}, {95, "Jack"}, {90, "Peter"}, {102, "Shawn"}, {97, "Mark"},
{93, "Hawk"}, {105, "Teddy"}, {108, "Bill"}, {92, "Clark"}, {98, "Demon"}, {110, "Bolt"},
{104, "Larry"}, {99, "Antony"}, {106, "Elvis"}, {103, "Frank"}};
int N = sizeof(list) / sizeof(Athlete);
qsort(list, N, sizeof(Athlete), CmpSpeed);
qsort(list, N, sizeof(Athlete), CmpName);