高性能計算:使用MPI多進程並行求解N皇后,並按進程順序輸出擺放方案


實驗環境 linux 這里我使用了超算習堂的雲主機https://easyhpc.net/personal-computer
0. N皇后問題並行算法說明:
在N×N格的國際象棋上擺放N個皇后,使其不能互相攻擊,即任意兩個皇后都不能處於同一行、同一列或同一斜線上,問有多少種擺法?
要求:要求N>8,進(線)程數P取等於N,小於N,大於N等不同情況。所有進(線)程依次將滿足條件的排法個數輸出。進(線)程0先輸出,進(線)程1再輸出,進(線)程P-1最后輸出。

  1. 更新軟件包列表
    sudo apt-get update
  2. 配置基礎編譯環境
    sudo apt-get install -y build-essential
    下載與安裝完成后,可鍵入下面的命令觀察gcc版本信息。
    gcc -v
  3. MPICH的安裝
    sudo apt-get install -y mpich
    mpicc -v
    安裝完畢驗證
  4. 環境搭建完成,編寫代碼如下:
    N皇后問題的並行算法求解,算法思路參考了> 八皇后問題mpi求解方案> 建議對Nqueen不了解的同學去看看這篇。

,為了滿足多個進程順序輸出其搜索到的方案的要求,其前面的連接基礎上,我的代碼加入了阻塞通訊,構造了一個ostringstram oss保存單個進程搜索到的方案數, 可以使得多個進程之間進行通訊。進程1將oss輸出后,向下一進程send消息,下一進程會堵塞,知道接收到上一進程的消息后(也就是上一進程完成了輸出),輸出自己的方案並且自己發送消息到自己的下一個進程。

  1. C++實現
點擊查看代碼
#include <chrono>
#include <cstring>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <thread>
#ifndef MPICH_SKIP_MPICXX
#define MPICH_SKIP_MPICXX
#endif
#include <mpi.h>

using std::cout;

using std::endl;

using std::clog;

using std::ofstream;

using std::ifstream;
using std::ostringstream;
using std::string;

int N = 8;
double start, end;

void outPut(const int &size, int *array, ostringstream &file) //輸出到文件
{
     int myid;
     MPI_Comm_rank(MPI_COMM_WORLD, &myid);
     file << "myId: " << myid << ":";
     for (int i = 0; i < size; i++)
     {
          file << array[i];
     }
     file << "\n";
}

bool isContact(const int &deep, const int *const &array) //判斷對角線上是否有沖突
{
     int temp;
     for (int i = 1; i < deep + 1; i++)
     {
          temp = array[deep - i];
          if (array[deep] - i == temp || array[deep] + i == temp) //這條語句完成判斷
               return true;
     }
     return false;
}

void range(const int &size, const int &deep, int *const &flags, int *&array, int &count, ostringstream &file) //進行遞歸推導
{
     for (int i = 0; i < size; i++) //從第到第個,判斷是否還有沒用過並且沒有沖突的數據
     {
          if (!flags[i]) //判斷是否被用過,這里使用的是按內容尋址的方式
          {
               array[deep] = i; //如果i沒有被使用過,那么現在使用i
               if (deep != 0)   //不是第一行的元素要判斷對角線上是否有沖突
               {
                    if (isContact(deep, array)) //判斷對角線是否有沖突,主要deep是層次
                         continue;              //如果有沖突,繼續循環,尋找下一個試探點
               }
               flags[i] = 1;         //目前第i個點可用,這里進行標記,第i個點已經被占用了
               if (deep == size - 1) //當深度為,就是找到了一個序列,滿足八皇后要求了
               {
                    outPut(size, array, file); //將結果輸出到文件
                    count++;                   //次數加一
               }
               else //沒有找全所有的棋子
               {
                    range(size, deep + 1, flags, array, count, file); //進一步遞歸調用,完成沒完成的棋子
                    array[deep] = -1;                                 //遞歸回來,要恢復原狀,以備下次繼續遞歸到新的序列
               }
               flags[i] = 0; //恢復標志
          }
     }
}

void mpi_range(const int &size, int *&flags, int *&array, const int &myId) //mpi開啟遞歸調用
{
     flags = new int[N];                //兩個數據結構,flags用來存儲位置是否被占用信息
     array = new int[N];                //存儲第i行的棋子放的位置
     memset(flags, 0, sizeof(int) * N); //賦初值
     memset(array, -1, sizeof(int) * N);
     flags[myId] = 1; //第i個進程執行第一行為i的計算
     array[0] = myId;
     int count = 0;      //計數為
     int totalCount = 0; //總計數為
     ostringstream file;
     range(N, 1, flags, array, count, file);                                  //開始遞歸
     MPI_Reduce(&count, &totalCount, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD); //規約所有遞歸結構

     if (myId == 0) //主進程輸出計算結果
     {
          MPI_Recv(&deley, 1, MPI_INT, size - 1, 0, MPI_COMM_WORLD, &status2);
          end = MPI_Wtime();
          cout << "totalCount: " << totalCount << endl;
          cout << "Runtime =:" << end - start << endl;
     }
     MPI_Status status;
     int deley = 0;
     if (myId == 0)
     {
          cout << file.str();
          // cout<<myId;
          MPI_Send(&deley, 1, MPI_INT, myId + 1, 0, MPI_COMM_WORLD);
     }
     if (myId >= 1)
     {
          MPI_Recv(&deley, 1, MPI_INT, myId - 1, 0, MPI_COMM_WORLD, &status);
          cout << file.str();
          //  cout<<myId;
          if (myId + 1 <= size)
          {
               MPI_Send(&deley, 1, MPI_INT, (myId + 1) % size, 0, MPI_COMM_WORLD);
          }
     }
}

int main(int argc, char *argv[])
{
     int size;
     int myId;
     MPI_Init(&argc, &argv); //初始化mpi
     start = MPI_Wtime();
     MPI_Comm_size(MPI_COMM_WORLD, &size); //獲取開啟的進程數量
     N = size;
     MPI_Comm_rank(MPI_COMM_WORLD, &myId); //獲取當前進程的id號
     int *flags;                           //標記
     int *array;                           //存放棋盤次序
     mpi_range(N, flags, array, myId);     //開始遞歸計算
     MPI_Finalize();                       //計算終止
     return 0;
}

  1. 編譯mpixx ./Nqueen.cpp -o ./Nqueen
  2. 運行mpirun -np 8 ./Nqueen
  3. 運行結果 運行結果


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM