习题5.1
11.Tromino谜题 Tromino是一个由棋盘上的三个1×1方块组成的L型骨牌。我们的问题是,如何用Tromino覆盖一个缺少了一个方块的2n×2n棋盘。除了这个缺失的方块,Tromino应该覆盖棋盘上的所有方块,Tromino可以任意转向但不能有重叠。
为此问题设计一个分治算法。
分析:n>0时,可将2n×2n的棋盘划分为4个2n-1×2n-1的子棋盘。这样划分后,由于原棋盘只有一个特殊方格,所以,这4个子棋盘中只有1个子棋盘中有特殊方格,其余3个子棋盘中没有特殊方格。为了将这3个没有特殊方格的子棋盘转化成为特殊棋盘,以便采用递归方法求解,可以用一个L型骨牌覆盖这3个较小的棋盘的会合处,从而将原问题转化为4个较小规模的棋盘覆盖问题。递归地使用这种划分策略,直至将棋盘分割为1×1的子棋盘。
#include<iostream>
#include<cmath>
using namespace std;
int a[1000][1000];//棋盘d
int c;//骨牌的号码
/* tr和tc是棋盘左上角的下标,dr和dc是特殊方格的下标,size是棋盘的大小 */
void Tromino(int tr, int tc, int dr, int dc, int size)
{
if (size == 1)
return;
int t = c++;
int s = size / 2;
//左上角
if (dr < tr + s&&dc < tc + s)
Tromino(tr, tc, dr, dc, s);
else
{
a[tr + s - 1][tc + s - 1] = t;
Tromino(tr, tc, tr + s - 1, tc + s - 1, s);
}
//右上角
if (dr < tr + s&&dc >= tc + s)
Tromino(tr, tc + s, dr, dc, s);
else
{
a[tr + s - 1][tc + s] = t;
Tromino(tr, tc + s, tr + s - 1, tc + s, s);
}
//左下角
if (dr >= tr + s&&dc < tc + s)
Tromino(tr + s, tc, dr, dc, s);
else
{
a[tr + s][tc + s - 1] = t;
Tromino(tr + s, tc, tr + s, tc + s - 1, s);
}
//右下角
if (dr >= tr + s&&dc >= tc + s)
Tromino(tr + s, tc + s, dr, dc, s);
else
{
a[tr + s][tc + s] = t;
Tromino(tr + s, tc + s, tr + s, tc + s, s);
}
}
int main()
{
int k;
int x, y;
printf("输入数据请自行保证n值大于等于1,下标不越界!!!\n");
printf("请分别输入n值,特殊方格下标,输入3个0表示结束:\n");
while (scanf("%d%d%d", &k, &x, &y) && (k || x || y))
{
//init
c = 1;
int num = pow(2, k);
memset(a, 0, sizeof(a));
Tromino(0, 0, x, y, num);
//print
for (int i = 0; i < num; i++)
{
for (int j = 0; j < num; j++)
printf("%-3d ", a[i][j]);
printf("\n");
}
printf("\n");
}
return 0;
}
习题5.2
11.螺钉和螺母问题 假设我们有n个直径各不相同的螺钉以及n个相应的螺母。我们一次只能比较一对螺钉和螺母,来判断螺母是大于螺钉 、小于螺钉还是正好适合螺钉。然而,我们不能拿两个螺母作比较,也不能拿两个螺钉作比较。我们的问题是要找到每一对匹配的螺钉和螺母。为该问题设计一个算法,它的平均效率必须属于集合θ(nlogn)。
分析:随便选一个螺钉,然后在螺母中找到匹配的那个,并把螺母分成大于与小于该螺钉的两组。然后根据匹配的螺母,把螺钉分成两组。遍历,直到全部排好序。
#include <iostream>
#include <algorithm>
#include <iomanip>
using namespace std;
void print(int* array, int n)
{
for (int i = 0; i < 7; ++i)
{
std::cout << left;
std::cout << setw(3) << array[i] << " ";
}
std::cout << std::endl;
}
//---------------------------------------------------
int get_random_nut(int* nuts, int left, int right, int n)
{
if (!nuts || right < left) return -1;
return nuts[left + n];
}
//---------------------------------------------------
int get_match_bolt(int* bolts, int nut_pivot, int left, int right, int& index)
{
if (!bolts || right < left) return -1;
for (int i = left; i <= right; ++i)
{
if (bolts[i] == nut_pivot)
{
index = i;
return bolts[i];
}
}
return -1;
}
//---------------------------------------------------
int partition(int pivot, int index, int* array, int left, int right)
{
if (!array || right <= left) return -1;
int indexl(left), indexr(right);
while (indexl <= indexr)
{
while (array[indexl] <= pivot && indexl <= indexr) { ++indexl; }
while (array[indexr] >= pivot && indexl <= indexr) { --indexr; }
if (indexl <= indexr)
{
std::swap(array[indexl], array[indexr]);
++indexl;
--indexr;
}
}
indexr = max(indexr, left);
std::swap(array[indexr], array[index]);
return indexr;
}
//---------------------------------------------------
void match_nut_bolt(int* nuts, int*bolts, int left, int right)
{
// set bolt pivot and it's position, get nut pivot and it's position
int bolt_pos(0);
int nut_pivot = get_random_nut(nuts, left, right, 0);
int bolt_pivot = get_match_bolt(bolts, nut_pivot * 10, left, right, bolt_pos);
// partition
int index_nut = partition(bolt_pivot / 10, 0 + left, nuts, left, right);
print(nuts, 7);
int index_bolt = partition(nut_pivot * 10, bolt_pos, bolts, left, right);
print(bolts, 7);
if (index_bolt != index_nut)
{
std::cout << "error" << std::endl;
}
// recursive
if (left < index_nut - 1)
{
match_nut_bolt(nuts, bolts, left, index_nut - 1);
}
if (right > index_nut + 1)
{
match_nut_bolt(nuts, bolts, index_nut + 1, right);
}
}
//---------------------------------------------------
void match_nut_bolt(int* nuts, int* bolts, int n)
{
if (!nuts || !bolts || n < 2) return;
match_nut_bolt(nuts, bolts, 0, n - 1);
}
int main()
{
int nuts[7] = { 5, 4, 6, 2, 7, 1, 3 };
int bolt[7] = { 40, 10, 20, 50, 30, 70, 60 };
//int nuts[7] = {4,1,2,5,3,7,6};
//int bolt[7] = {50,40,60,20,70,10,30};
match_nut_bolt(nuts, bolt, 7);
std::cout << "nuts: ";
print(nuts, 7);
std::cout << "bolts: ";
print(bolt, 7);
std::cout << std::endl;
system("Pause");
}
习题5.3
11.巧克力块谜题 有一块n×m格的巧克力,我们要把它掰成n×m个1×1的小块。我们只能沿直线掰,而且不能几块同时掰。设计一个算法用最少的次数掰完巧克力,该次数是多少?用二叉树的特性来论证答案。
解答:掰开巧克力块的过程可以用一棵完全二叉树来表示,父节点表示可以掰开的巧克力,叶子节点表示1×1的巧克力。根据性质,内部结点数目为n*m-1,也就是掰的次数。
习题5.5
11.创建十边形 在平面上有1000个点,并且任意3个点不在同一条直线上。设计一个算法来构造100个十边形,使得十边形的点落在平面上的1000个点上。十边形不必是凸多边形,但必须是简单多边形,也就是说它的边不能够交叉,并且任意两个十边形没有公共点。
解答:给这1000个点按横坐标大小排序,我们首先用前10个点构造多边形。如下图所示,将P1和P10连成直线,判断各点在直线上方还是下方:若都在同一边,则按照标号顺序依次守卫连接起来则构成多边形;若两边都有,则分别从上方和下方,从P1到P10按标号从小到大连线,构成一个多边形。
剩余的99个十边形也按照上述算法进行构造。由于各点按照横坐标排好序,所以各个多边形不会相交。
12.最短路径 在二维欧几里得平面上有一块围起来的区域,它的形状是一个凸多边形,多边形的顶点位于 ,还有另外两个点
和
,满足}
,而且
。设计一个高效的算法来计算a和b之间最短路径的长度。
解答:由于该凸边形是一个围起来的区域,所以从a到b无法从区域内部穿过。利用快包算法求出点集合{a,b,p1,p2…,pn} 的上包和下包,然后计算两者中距离较短的为最短路径。