选择算法:在一个未排序的的序列中,选取第K小的元素。
static int random_select(int begin, int end, int k)
{
assert(begin < end);
assert(k >= 0);
assert(k < end-begin);
int rand = randint(begin, end);
int left, right;
partition(begin, end, rand, &left, &right);
int idx = k+begin;
if (idx < left)
return random_select(begin, left, k);
else if (idx >=left && idx<=right)
return idx;
else
return random_select(right+1, end, idx-right-1);
}
static void partition(int begin, int end, int pivot_index, int *ret_left, int *ret_right)
{
#if 0
printf("***********in partition*****************\n");
print_array(begin, end);
printf("pivot_index = %d\n", pivot_index);
#endif
int pivot = A[pivot_index];
swap(begin, pivot_index);
int left = begin;
int right = begin;
int i;
for (i=begin+1; i<end; i++)
{
if (A[i] > pivot)
;
else if (A[i] == pivot)
{
swap(right+1, i);
right += 1;
}
else
{
swap(left, i); left += 1;
swap(right+1, i); right += 1;
}
}
*ret_left = left;
*ret_right = right;
#if 0
print_array(begin, end);
printf("left = %d, right = %d \n", left, right);
printf("***********end partition*****************\n");
#endif
}
用pseudo code写完,我会用了不到5分钟,而实际转换成代码调试,却花费了我半个多小时。以下是几点原因。
1. 平常写pseudo code时,是和人的正常思维一直,数组开始于1,最小的第k个元素就是最小的第k个元素。而实际写代码是,数组时从0开始的。这一点,很容易混淆。我的感觉是,定义一套自己的符号和规则,使得pseudo code和程序代码的约定一致。比如,排名可以从0开始,第0小,第1小,..,第k小。比如,集合统一采取半开半闭的表示方法,所有有关数组的调用传入的参数(begin,end)都是半开半闭的.A[begin:end)
2. 用divide-and-conque方法解决问题时,边界条件很容易搞错。我认为,在写边界条件时,最好用简单示例看看对不对。否则,等程序跑起来,崩溃了,就悲剧了。
3. 函数的定义要明确,其表示的含义一定要明确。传入的参数的意义,返回的值的意义。我这次有一个bug就是因为random_select函数一开始定义返回的值的含义没有明确。
磨刀不误砍柴工,定义明确了再动手。