算法设计与分析基础 第五章谜题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_30432997/article/details/84141387

习题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} 的上包和下包,然后计算两者中距离较短的为最短路径。

猜你喜欢

转载自blog.csdn.net/qq_30432997/article/details/84141387