2-2:第5个应该是对的,其余的不是死循环就是搜索结果不正确。
2-3:每次记录middle的位置,再注意(x < a[0])和(x > a[n-1])的情况即可。
2-8:每次比较a[middle]与middle的值即可:
while( left <= right )
{
middle = (left+right) / 2;
if( middle == a[middle] ) return middle;
else if( middle > a[middle] ) left = middle + 1;
else right = middle - 1;
}
2-11:每个元素的下一个位置是可以计算出来的,令t = n - 1 - k; 则第i个元素将被移到第(i+t)%n个位置上去,算法开始从第0个位置开始,只要保证移动的位置没有重复并且移动了n次则退出,否则从1个位置开始移动直到移动了n次,如果循环就以此类推。(不必判断每个元素是否移动过,因为在移动n次前不会有重复的元素),因此O(n)的时间,O(1)的空间:
#include
#define N 100
int main()
{
int a[N], n, k, i, j, temp, count;
while( scanf( "%d%d", &n, &k ) != EOF )
{
for( i = 0; i < n; i++ )
scanf( "%d", a + i );
k = n - 1 - k; //用来计算元素的下一个位置
for( i = 0, count = 0; i < n && count < n; i++ ) //count用来记录所移动的次数
{
temp = a[i], j = (i+k) % n;
while( j != i ) //判断是否产生了循环
{
a[j]^=temp^=a[j]^=temp; //交换两个数,temp记录下一个位置的数。
j = (j+k) % n;
count++;
}
a[j]^=temp^=a[j]^=temp;
count++;
}
for( i = 0; i < n; i++ )
printf( "%d ", a[i] );
printf( "/n" );
}
return 0;
}
2-15:分组比较即可。
#include
void MaxMin( int, int, int&, int& );
int a[100];
int main()
{
int max, min, n, i;
while( scanf( "%d", &n ) != EOF )
{
for( i = 0; i < n; i++ )
scanf( "%d", a + i );
MaxMin( 0, n-1, max, min );
printf( "%d %d/n", max, min );
}
return 0;
}
void MaxMin( int i, int j, int &max, int &min )
{
int middle;
int x1, y1, x2, y2;
if( i == j )
{ max = min = a[i]; return; }
if( j - i == 1 )
{
if( a[i] > a[j] ) max = a[i], min = a[j];
else max = a[j], min = a[i];
return;
}
middle = (i+j) / 2;
MaxMin( i, middle, x1, y1 );
MaxMin( middle+1, j, x2, y2 );
if( x1 > x2 )
{ max = x1; }
else
{ max = x2; }
if( y1 > y2 )
{ min = y2; }
else
{ min = y1; }
}
2-16:和锦标赛排序类似的思想。第一次找最小值是n-1,然后找次小值根据以前的信息,由完全二叉树的高度为[logn]+1,则可以得出比较次数为[logn]-1,则总比较次数为n + [logn] - 2.
2-17:对于一个集合,O(n)的扫描即可,每次排好一个位置。数组下标从1开始。
for (i = 1; i <= n; )
if (a[i] != i)
{
temp = a[a[i]];
a[a[i]] = a[i];
a[i] = temp;
}
else
i++;
k个集合复杂度为O(kn)(k为常数)。
2-22:经典的方法:
for (i = 0; i < n; i++)
swap(a[i], a[rand()%n]);
2-30:只要找出y坐标的中位数即可:
找中位数的方法:
#include
#include
#include
#define N 100
void Rand_select( int*, int, int );
int partition( int*, int, int );
int swap( int&, int& );
int k, ans;
int main()
{
int n, a[N], i;
while( scanf( "%d", &n ) != EOF )
{
srand(time(NULL));
k = n/2;
for( i = 0; i < n; i++ )
scanf( "%d", a + i );
Rand_select( a, 0, n-1 );
printf( "%d/n", ans );
}
return 0;
}
void Rand_select( int a[], int p, int q )
{
int m;
if (p <= q)
{
m = partition( a, p, q );
if( k == m )
{ ans = a[m]; return; }
else if( k > m )
Rand_select( a, m+1, q );
else
Rand_select( a, p, m-1 );
}
}
int partition( int a[], int p, int q )
{
int last, i;
if (p != q)
swap( a[rand()%(q-p)+p], a[p] );
for( i = p+1, last = p; i <= q; i++ )
if( a[i] < a[p] )
swap( a[i], a[++last] );
swap( a[last], a[p] );
return last;
}
int swap( int &p, int &q )
{
int temp = p;
p = q;
q = temp;
return 0;
}
2-31:Hash或BST都可以,以下给出BST的代码O(nlogn)
#include
#include
#define N 100
typedef struct TYPE
{
struct TYPE *left, *right;
int times, no; //times记录每个元素出现的次数,no记录每个无数的值。
}Node;
int a[N], n, max, key;
Node *root;
void add( Node**, int );
void search( Node* );
int main()
{
int i;
while( scanf( "%d", &n ) != EOF )
{
root = NULL, max = 0;
for( i = 0; i < n; i++ )
{
scanf( "%d", a + i );
add( &root, a[i] ); //将每个结点加入到二叉树中(O(nlogn))
}
search( root ); //中序遍历找出出现次数最多的元素。(O(n))
printf( "%d/n", key );
}
return 0;
}
void add( Node **r, int m )
{
if( *r == NULL )
{
(*r) = (Node*)malloc(sizeof(Node));
(*r)->times = 1;
(*r)->no = m;
(*r)->right = (*r)->left = NULL;
return;
}
else if( m == (*r)->no )
{
(*r)->times++;
return;
}
else if( m > (*r)->no )
add( &((*r)->right), m );
else
add( &((*r)->left), m );
}
void search( Node *r )
{
if( r != NULL )
{
search( r->left );
if( r->times > max ) max = r->times, key = r->no;
search( r->right );
}
}
2-33:这题用分治法感觉有点勉强,回溯+贪心已经非常快了。下面是一种实现,方法不是很好。
#include
#include
#include
#define N 20
typedef struct TYPE
{
int x, y, w;
}PRI;
int di[8][2] = {{-2,1}, {-1,2}, {1,2}, {2,1}, {2,-1}, {1,-2}, {-1,-2}, {-2,-1}};
int n, flag, used[N][N], ans[N][N]; //used记录某个点是否已经走过, ans记录第i步的坐标。
PRI Queue[N*N][8]; //优先队列的一种,8个方向的位置按出口度排序。
void search( int, int, int );
int swap( int*, int* );
int Weight( int, int );
int main()
{
int i, j;
while( scanf( "%d", &n ) != EOF )
{
memset( used, 0, n*n*sizeof(used[0][0]) );
used[0][0] = ans[0][0] = 1, flag = 0;
search( 1, 0, 0 );
if( !flag )
printf( "IMPOSSIBLE/n" );
else
{
for( i = 0; i < n; i++ )
{
for( j = 0; j < n; j++ )
printf( "%4d", ans[i][j] );
printf( "/n" );
}
}
}
return 0;
}
void search( int depth, int x, int y )
{
int a, b, i, j;
if( depth == n*n )
{
flag = 1;
return;
}
for( i = 0; i < 8; i++ )
{
Queue[depth][i].x = x+di[i][0];
Queue[depth][i].y = y+di[i][1];
Queue[depth][i].w = Weight( x+di[i][0], y+di[i][1] );
}
for( i = 0; i < 8; i++ )
for( j = i+1; j < 8; j++ )
if( Queue[depth][i].w < Queue[depth][j].w )
{
swap( &(Queue[depth][i].x), &(Queue[depth][j].x) );
swap( &(Queue[depth][i].y), &(Queue[depth][j].y) );
swap( &(Queue[depth][i].w), &(Queue[depth][j].w) );
}
for( i = 0; i < 8; i++ )
{
a = Queue[depth][i].x, b = Queue[depth][i].y;
if(flag) return;
if( a >= 0 && a < n && b >= 0 && b < n && !used[a][b] )
{
used[a][b] = 1;
ans[a][b] = depth + 1;
search( depth + 1, a, b );
used[a][b] = 0;
}
}
}
int Weight( int p, int q )
{
int i, a, b, count;
for( i = 0, count = 0; i < 8; i++ )
{
a = p+di[i][0], b = q+di[i][1];
if( a < 0 || a >= n || b < 0 || b >= n || used[a][b] )
count++;
}
return count;
}
int swap( int *p, int *q )
{
int temp = *p;
*p = *q;
*q = temp;
return 0;
}
这题下面的这个链接找到一篇论文(求马步图Hamilton圈的最优算法),用的所谓的分治-回溯-合并的方法,可以做到O(n^2)
http://202.109.195.141/chenyan/noi/noi.htm
2-34:这题明显也是可以构造的,就没再去想分治法了。
设n个二进位分别为:
a[0] a[1] a[2] a[3] ... a[n-1] 则相应的格雷码为:
a[0] a[0]^a[1] a[1]^a[2] ... 第0位直接写下来,然后两两异或即可。
2-35:直接构造就可以了:
设n=6, 则比赛的序列为:
1 2 3 4 5 6: 表示 1-6 2-5 3-4
1 6 2 3 4 5
1 5 6 2 3 4
1 4 5 6 2 3
1 3 4 5 6 2
当n为奇数时,比如n = 5时也是上面的序列,只要将和6的比赛轮空就可以了。
就先写这么多了,部分题就不写了,还有部分题有待思考。