算法整理1——全排列问题_二分搜索_递归解决棋盘覆盖问题

1. 全排列问题

问题描述:给你一个数字m,问1~m这几个数字有多少种排列方案,输出每一种排列方案。

解:这是一个全排列问题。解决这个问题可以采用深度优先搜索的思想,即先确定第一个位置的数字(将这个数字加入队列),在第一个数字确定的前提下,递归再确定第二个数字(将这个数字加入队列)……这样,直到确定第m个数字。这就是满足要求的方案中的一种,此时输出整个队列的数字,这也是递归的边界条件。下面给出C++实现代码:

#include
using namespace std;
bool vis[19];// 标记数组,用来标记当前数字是否可用
int qu[109];// 用来存储数字的队列
int cnt = 0;
// n代表当前是在确定第几个数字,m代表一共需要确定的数字的个数
void dfs(int n, int m){
	// 递归的边界条件,当你处理的数字位数超过了m,输出队列并返回
    if(n>m){
        for(int i=0;i<cnt;i++)
            cout<<qu[i]<<" ";
        cout<<endl;
        cnt--;
        return ;
    }
    // 从1~m遍历,看看当前数字是不是可以使用
    for(int i=1;i<=m;i++){
        if(vis[i]){
        	// 如果这个数字可用,使用这个数字并将这个数字设置为不可用
            vis[i] = false;
            // 入队
            qu[cnt++] = i;
            // 在确定了当前数字后,递归调用自己,来确定下一个数字
            dfs(n+1, m);
            /*
             这里非常重要,也最不好理解,在后面的数字确定了之后,
             开始返回,此时需要把当前这个数字重新置为true,表示可用。
             举个例子,假设m为4,第一次输出的队列为1234,如果在函数返回
             时,不将4重新置为true,则不会出现1243这种方案,最终结果就
             错了。
            */
            vis[i] = true;
        }
    }
    cnt--;
}
int main(){
	int m;
	// 初始标记数组全设置为true
    for(int i=0;i<19;i++)
        vis[i] = true;
    cin>>m;
    dfs(1, m);
    return 0;
}

2. 二分搜索

问题描述:给你一个有序数组,再给你一个数x,问x是否在这个数组中。

解:实际上,二分搜索的思路是很好理解的。问题的关键在于确保这个数组是有序的(如果可以排序的话即使输入时乱序也没有关系)。在确保数组有序的前提下,我们就可以采用每次比较数组索引中位数和x来缩小解的范围。举个栗子,加入你在玩猜数字游戏,告诉你答案在一到十之间,实际上答案是3,让你猜。你最好的策略就是第一次猜5,告诉你猜大了,之后猜3……也就是每次猜最中间的那个数,这样即使不对,也可以缩小一半的可能区间。因此,如果数组长度是n的话,你最次log2(n)次就一定能得到答案。因此,二分搜索的时间复杂度就是log2(n)。下面给出二分搜索的C++实现代码:

#include
#include
using namespace std;
int num[1009];
int main(){
    int n, x;
    bool flag = false;
    // 输入n个数字
    cin>>n;
    for(int i=0;i<n;i++)
        cin>>num[i];
    sort(num, num+n);
    // 输入要判断的数字x
    cin>>x;
    // 进行二分搜索
    int l = 0;// 左指针
    int r = n-1;// 右指针
    // 当左指针大于右指针时就没有必要再进行判断了
    while(l<=r){
        int mid = (l + r) / 2;
        if(x > num[mid])
            l = mid + 1;
        else if(x < num[mid])
            r = mid - 1;
        else{
            cout<<"找到了数字"<<x<<"!"<<endl;
            flag = true;
            break;
        }
    }
    if(!flag)
        cout<<"对不起,未找到数字"<<x<<"!"<<endl;
    return 0;
}

3. 递归解决棋盘覆盖问题

问题描述:给你一个棋盘,其中有一个特殊点(一个小方块),希望你用三个小方块组成的骨牌覆盖这个棋盘。

解:对于这个问题,我们的策略是首先将棋盘分成四个相同的小棋盘,对称分割。之后,对其中没有特殊点的其他三个小棋盘放置骨牌,只需要在最中间放置一个骨牌就可以覆盖三个棋盘。算法整理1——全排列问题_二分搜索_递归解决棋盘覆盖问题_第1张图片
再然后,递归处理这四个小棋盘就可以了。用这种策略,我们不断缩小了问题的范围(棋盘的大小),直到最终棋盘只有一个格子大小就覆盖完成了。我们用相同的数字标记棋盘上的三个格子来代表一块骨牌的覆盖。C++实现代码如下:

#include
using namespace std;
int tile = 1;
int Board[1009][1009];
void ChessBoard(int tr, int tc, int dr, int dc, int Size){
    if(Size == 1) return ;
    int t = tile++, s = Size / 2;

    if(dr < tr + s && dc < tc + s)
        ChessBoard(tr, tc, dr, dc, s);
    else{
        Board[tr + s - 1][tc + s - 1] = t;
        ChessBoard(tr, tc, tr + s - 1, tc + s -1, s);
    }

    if(dr < tr + s && dc >= tc + s)
        ChessBoard(tr, tc + s, dr, dc, s);
    else{
        Board[tr + s - 1][tc + s] = t;
        ChessBoard(tr, tc + s, tr + s - 1, tc + s, s);
    }

    if(dr >= tr + s && dc < tc + s)
        ChessBoard(tr + s, tc, dr, dc, s);
    else{
        Board[tr + s][tc + s - 1] = t;
        ChessBoard(tr + s, tc, tr + s, tc + s -1, s);
    }

    if(dr >= tr + s && dc >= tc + s)
        ChessBoard(tr + s, tc + s, dr, dc, s);
    else{
        Board[tr + s][tc + s] = t;
        ChessBoard(tr + s, tc + s, tr + s, tc + s, s);
    }
}
int main(){
    int tr, tc, dr, dc, Size;
    cout<<"tr=";
    cin>>tr;
    cout<<"tc=";
    cin>>tc;
    cout<<"dr=";
    cin>>dr;
    cout<<"dc=";
    cin>>dc;
    cout<<"Size=";
    cin>>Size;
    ChessBoard(tr, tc, dr, dc, Size);
    cout<<"棋盘覆盖成功!"<<endl;
    for(int i=tr;i<tr+Size-1;i++){
        for(int j=tc;j<tr+Size-1;j++){
            cout<<Board[i][j];
        }
        cout<<endl;
    }
    return 0;
}

PS:此篇博客重点在于给出实现代码,对于问题的描述以及解题思路的讲解可能不够详细全面,敬请谅解。

你可能感兴趣的:(算法整理,算法)