蟑螂隨機走動問題(資料結構圖問題)
一、問題概要
1.1 題 目:
《隨機走動》
1.2 初始條件:
一隻貪杯的蟑螂醺醺然在室內地面遊蕩。地面鋪滿方磚,共計m*n塊,構成大面積矩陣。蟑螂在方磚間隨機爬行,可能想撞大運,找片阿司匹林解酒。假定蟑螂從房間中央一塊方磚出發,除非撞牆,可以爬向相鄰8塊方磚的任意一塊,且機會均等。問蟑螂多久才可以在所有方磚上留下行跡?
1.3 要求完成的主要任務:
用模擬的方法模擬蟑螂的爬行方向。統計每次蟑螂爬滿所有方磚的爬行次數,將程式執行至少1000次,從結果中推導關於在給定m和n的情況下蟑螂爬滿方磚的期望值或區間。
1.4 輸入:
房間引數2<m≤20和2<n≤40,起點座標(x, y)。
1.5輸出:
蟑螂每達成目標一次,就輸出count陣列(稱為蟑螂的爬行密度)一次,並對count陣列求和。將每次的執行結果儲存下來,在程式執行至少1000次後,對結果進行統計歸納,指出蟑螂最可能需要多少次才能爬滿所有方磚。
二、概要設計
2.1抽象資料型別定義
(1)用於二維密度圖的構建資料型別:
typedef struct Walk_data { int count[MAX_HEIGHT][MAX_LENGTH]; }Walk_data_list;
(2)用於多種資料作為函式返回值的資料型別:
typedef struct Send_data
{
int first[MAX_HEIGHT][MAX_LENGTH];
int end[MAX_HEIGHT][MAX_LENGTH];
int times_count;
int mark;
int m;
int n;
}Send_data_list;
2.2邏輯結構
本程式當中共使用了兩種抽象資料型別,構建了3個附加函式,其中抽象資料型別。Walk_data用於構建記錄爬行密度的二維陣列圖,在主函式以及附加函式中均有呼叫,用於該類資料的統一;Send_data用於多種資料型別作為函式返回值同時返回的情況,主要運用於Crawl_all()函式以及Crawl_rest()函式;Crawl_all()函式是按需遍歷圖函式,該函式會將使用者初次輸入的資料進行遍歷輸出,同時還會記錄完成第一次遍歷的斷點資料以及完成需求次數後的資料;Crawl_rest()函式是在使用者輸入次數未能完全遍歷時呼叫的函式,該函式會繼續接著使用者輸入要求次數的最後一次圖密度情況進行爬行遍歷,直到完成第一次圖的遍歷;Draw_zhentai()函式是用來將第一次完全遍歷後的密度圖對應的二維正態直方圖切面進行視覺化輸出處理,得到對應的正態分佈直方圖。
三、詳細設計
3.1 資料型別
①抽象資料型別
typedef struct Walk_data
{
int count[MAX_HEIGHT][MAX_LENGTH];
}Walk_data_list;
②抽象資料型別
typedef struct Send_data
{
int first[MAX_HEIGHT][MAX_LENGTH];
int end[MAX_HEIGHT][MAX_LENGTH];
int times_count;
int mark;
int m;
int n;
}Send_data_list;
③整型型別(用於定義爬行的方向)
int imove[8] = { -1,0,1,1,1,0,-1,-1 };
int jmove[8] = { 1,1,1,0,-1,-1,-1,0 };
④整型型別(用於定義初始的位置)
int m = (int)(max_height / 2);
int n = (int)(max_length / 2);
⑤整型型別(用於定義輸入的內容)
int max_height ;
int max_length ;
int loop_times ;
⑥整型型別(用於斷點標識的設定)
int u = 0;
int mark = 1;
⑥其它整數型別的定義
int times_loop;
int first_times;
int continues_times = loop_times;
int save_map_data[20];
int tmp_map_data[20];
int tmp_num;
int times = back_data.times_count;
int mark = back_data.mark;
int m = back_data.m;
int n = back_data.n;
int get_order;
3.2 核心操作實現
①Crawl_all函式核心(虛擬碼)
Send_data_list Crawl_all(Walk_data_list pic, int max_height, int max_length, int loop_times)
{
定義爬行方向
定義初始化位置
定義資料儲存返回結構
Send_data_list Send_back;
隨機數定義
斷點標誌定義
獲取迴圈次數
for (times_loop = 0; times_loop < loop_times; times_loop++)
{
檢測標記歸操作
隨機生成方向
撞牆操作
if (!(0 <= m && m < max_height && 0 <= n && n < max_length))
{
撞牆還原一步
}
pic.count[m][n] = pic.count[m][n] + 1;
//檢測是否遍歷完成
if (mark == 1) {
for (int k = 0; k < max_height; k++)
{
檢測操作
}
if (mark == 1) {
儲存資料矩陣first
Send_back.first[k][j] = pic.count[k][j];
}
歸零標記
}
}
}
for (int k = 0; k < max_height; k++)
{
for (int j = 0; j < max_length; j++)
{
儲存資料矩陣end
}
}
函式返回設定1.迴圈次數 2.是否遍歷完成標記 3.兩個斷點的二維陣列
Send_back.times_count = first_times;
Send_back.mark = mark;
Send_back.m = m;
Send_back.n = n;
return Send_back;
}
②Crawl_rest函式核心(虛擬碼)
Send_data_list Crawl_rest(Walk_data_list pic, int max_height, int max_length, int m, int n, int loop_times)
{
定義前行方向
Send_data_list Send_back;
定義隨機數
定義斷點標註
獲取已經迴圈的次數
do
{
檢測標記歸0
mark = 1;
隨機生成方向
撞牆操作
if (!(0 <= m && m < max_height && 0 <= n && n < max_length))
{
撞牆還原一步
}
pic.count[m][n] = pic.count[m][n] + 1;
continues_times++;
檢測是否遍歷完成
if (mark == 1) {
for (int k = 0; k < max_height; k++)
{
for (int j = 0; j < max_length; j++)
{
如果陣列存在0資料則讓mark遞增操作
}
}
if (mark == 1) {
for (int k = 0; k < max_height; k++)
{
for (int j = 0; j < max_length; j++)
{
儲存資料矩陣first
}
}
break;
}
}
} while (1);
返回遍歷完成資料(二維密度圖及迴圈次數)
}
③Draw_zhengtai()函式核心(虛擬碼)
int Draw_zhengtai(Walk_data_list pic, int max_height, int max_length)
{
定義切面位置
定義正態二維圖
定義切面圖最高資料項
for (int i = 0; i < max_height; i++)
{
save_map_data[i] = pic.count[i][n];
tmp_map_data[i] = pic.count[i][n];
}
for (int i = 0; i < max_height-1; i++)
{
if (tmp_map_data[i] >= tmp_map_data[i + 1])
{
一趟氣泡排序求最大值
}
}
儲存最大值
for (int i = 0; i < max_height; i++)
{
for (int j = 0; j < highest; j++)
{
全圖方塊遍歷
}
}
for (int i = 0; i < max_height; i++)
{
for (int j = 0; j < highest-save_map_data[i]; j++)
{
依據密度結構進行空白補充
}
}
切面正態直方圖輸出
return 切面圖最大資料項;
}
四、除錯分析
4.1 開發中問題及解決
①問題一:在初次構建按需遍歷函式時,發現有時,即使執行很多次,二維密度圖中的相對密度也會特別低,而且有時會讓程式直接退出導致程式崩潰。
這是因為由中心出發的蟑螂進行爬行,在爬行數次後很有可能會到達二維圖的邊緣,這個時候就有可能會爬到圖的外面,從而使得爬行密度二維圖傳送陣列越界,從而導不能繼續遍歷,甚至是程式崩潰。
而這一問題的解決方法就是,設定撞牆操作,在蟑螂“撞牆”的時候進行該補的還原,重複操作,直到下一步不撞牆位置。
②問題二:在進行功能完善的時候,希望設定斷點,分別記錄不同遍歷狀態的資料,這個偶就必須對爬行密度圖進行檢查,看它是否已經完成一次遍歷。
解決這個問題的方法就是,通過設定mark標記,對二維圖進行遍歷查“0”,如果存在“0”則執行mark++,再進行檢查如果mark==0則斷點找到,如果mark!=0 則將mark歸零繼續進行操作,知道找到斷點為止。
③問題三:在附加的功能函式中,需要對多種資料進行操作,而為了方便主函式進行操作結果的呼叫,就最好能實現多種資料型別的返回。
為了解決這個問題,我特意設定了對應的返回值結構體,定義了多種結構型別,從而滿足了返回值的要求。同時如果僅需要返回一個數組,則可以通過指標傳遞的方式進行。
④問題四:為了方面問題的模型化,以及對相關重要資料的抽取,在定義蟑螂初始位置的時候選擇的是爬行密度圖的“相對中心點”,由於涉及到對二維正態直方圖的分析,所以資料應該中心化,但怎樣才能找到完全合適的連續抽取演算法,且適用於不同規格的行數和列數(因為奇數行,偶數行,奇數列,偶數列組合後對應的中心相對位置不完全一樣)
我發現,對於任意一種行列結構,從中心開始,只要向下先取,就可以完成連續行進的資料遍歷。
⑤問題五:在做資料分析的過程中,我發現,整個二維的爬行密度圖中對應的資料,確實可以整合成一個大致的多維正態直方圖,但這樣的一個正態分佈圖應該是有Z軸建模的,所以在平面的資料視覺化中很難表現。針對整體資料相對正確的表示如下圖所示:
但在命令列中的輸出中受到限制,通過觀察可以看到,這樣一個三維模型,其中心切面正好是一個二維的正態分佈圖,而且具有對稱性,可以在一定程度上反映出資料的大致分佈情況。因此,在對資料的處理中,我選擇了爬行密度圖中心對應的列為基準資料列,進行資料視覺化處理。
這裡同樣還是採用的二維陣列的方式,只不過陣列的每一個元素變成了空白或者是方塊,這樣輸出以後就可以形成一個大概的直方圖。如下圖所示:
在這裡,由於我還想做進一步的資料分析,想找到這些資料中的最大值,這個時候就用到了一趟氣泡排序求取最大值的方法。
4.2 主要演算法時空分析
在整個程式中,最複雜的結構只有兩層迴圈,以氣泡排序為例分析,可得演算法的時間複雜度為O(n2),空間複雜度為O(1)。
五、使用者使用說明
5.1 按行次需求完成地圖遍歷的情況
- 蟑螂第一次爬行完成所有磚塊的資料:一個二維矩陣back_data.first[k][j]
- 蟑螂爬行輸入要求次數後的資料:一個二維矩陣back_data.end[k][j]
- 蟑螂完成所有磚塊的爬行一共進行了times次
- 輸入4則繼續獲取完成要求次數後的大致正態直方圖 (以下是依據爬行密度得出的二維正態分佈圖的切面圖map_pic[i][j]正態切面的最大值為zhengtai_map)
- 輸入5則重新開始執行次程式
- 輸入6則退出程式
5.2 按行次需求未完成遍歷的情況
- 蟑螂爬行輸入要求次數後的資料:一個二維矩陣back_data.end[k][j]
- 共進行了times次爬行,但沒有完成全部磚塊的遍歷
- 輸入4則繼續爬行完成一次遍歷
- 輸入5則繼續獲取完成要求次數後的大致正態直方圖 (以下是依據爬行密度得出的二維正態分佈圖的切面圖map_pic[i][j]正態切面的最大值為zhengtai_map)
- 輸入6則重新開始執行次程式
- 輸入7則退出程式
六、測試結果
6.1 測試一(5*5)
①未完成遍歷
②完成遍歷
6.2 測試二(10*10)
①未完成遍歷
②完成遍歷
七、原始碼(C語言)
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
//對矩陣各維長度進行限制
#define MAX_LENGTH 20
#define MAX_HEIGHT 20
typedef struct Walk_data
{
int count[MAX_HEIGHT][MAX_LENGTH];
}Walk_data_list;
typedef struct Send_data
{
int first[MAX_HEIGHT][MAX_LENGTH];
int end[MAX_HEIGHT][MAX_LENGTH];
int times_count;
int mark;
int m;
int n;
}Send_data_list;
//遍歷爬行函式
Send_data_list Crawl_all(Walk_data_list pic, int max_height, int max_length, int loop_times)
{
//定義爬行方向
int imove[8] = { -1,0,1,1,1,0,-1,-1 };
int jmove[8] = { 1,1,1,0,-1,-1,-1,0 };
int m = (int)(max_height / 2);
int n = (int)(max_length / 2);
//定義資料儲存結構
Send_data_list Send_back;
//初始化位置
pic.count[m][n] = 1;
srand(time(NULL));
int u = 0;
int mark = 1;
int times_loop;
int first_times;
for (times_loop = 0; times_loop < loop_times; times_loop++)
{
//檢測標記歸0
if (mark != 0)
{
mark = 1;
}
//隨機生成方向
u = rand() % 8;
m = m + imove[u];
n = n + jmove[u];
//撞牆操作
if (!(0 <= m && m < max_height && 0 <= n && n < max_length))
{
times_loop = times_loop - 1;
m = m - imove[u];
n = n - jmove[u];
continue;
}
pic.count[m][n] = pic.count[m][n] + 1;
//檢測是否遍歷完成
if (mark == 1) {
for (int k = 0; k < max_height; k++)
{
for (int j = 0; j < max_length; j++)
{
if (pic.count[k][j] == 0)
{
mark++;
}
}
}
if (mark == 1) {
first_times = times_loop + 1;
for (int k = 0; k < max_height; k++)
{
for (int j = 0; j < max_length; j++)
{
//儲存資料矩陣first
Send_back.first[k][j] = pic.count[k][j];
}
}
mark = 0;
}
}
}
//儲存資料矩陣end
for (int k = 0; k < max_height; k++)
{
for (int j = 0; j < max_length; j++)
{
Send_back.end[k][j] = pic.count[k][j];
}
}
//函式返回1.迴圈次數 2.是否遍歷完成標記 3.兩個斷點的二維陣列
Send_back.times_count = first_times;
Send_back.mark = mark;
Send_back.m = m;
Send_back.n = n;
return Send_back;
}
Send_data_list Crawl_rest(Walk_data_list pic, int max_height, int max_length, int m, int n, int loop_times)
{
int imove[8] = { -1,0,1,1,1,0,-1,-1 };
int jmove[8] = { 1,1,1,0,-1,-1,-1,0 };
Send_data_list Send_back;
srand(time(NULL));
int u = 0;
int mark = 1;
int continues_times = loop_times;
do
{
//檢測標記歸0
mark = 1;
//隨機生成方向
u = rand() % 8;
m = m + imove[u];
n = n + jmove[u];
//撞牆操作
if (!(0 <= m && m < max_height && 0 <= n && n < max_length))
{
m = m - imove[u];
n = n - jmove[u];
continue;
}
pic.count[m][n] = pic.count[m][n] + 1;
continues_times++;
//檢測是否遍歷完成
if (mark == 1) {
for (int k = 0; k < max_height; k++)
{
for (int j = 0; j < max_length; j++)
{
if (pic.count[k][j] == 0)
{
mark++;
}
}
}
if (mark == 1) {
for (int k = 0; k < max_height; k++)
{
for (int j = 0; j < max_length; j++)
{
//儲存資料矩陣first
Send_back.end[k][j] = pic.count[k][j];
}
}
break;
}
}
} while (1);
Send_back.times_count = continues_times;
return Send_back;
}
int Draw_zhengtai(Walk_data_list pic, int max_height, int max_length)
{
int m = (int)(max_height / 2);
int n = (int)(max_length / 2);
int save_map_data[20];
int tmp_map_data[20];
int tmp_num;
for (int i = 0; i < max_height; i++)
{
save_map_data[i] = pic.count[i][n];
tmp_map_data[i] = pic.count[i][n];
}
int highest;
//一趟氣泡排序求最大值
for (int i = 0; i < max_height-1; i++)
{
if (tmp_map_data[i] >= tmp_map_data[i + 1])
{
tmp_num = tmp_map_data[i+1];
tmp_map_data[i + 1] = tmp_map_data[i];
tmp_map_data[i] = tmp_num;
}
}
highest = tmp_map_data[max_height - 1];
int map_pic[200][20];
for (int i = 0; i < max_height; i++)
{
for (int j = 0; j < highest; j++)
{
map_pic[j][i] = "▅";
}
}
for (int i = 0; i < max_height; i++)
{
for (int j = 0; j < highest-save_map_data[i]; j++)
{
map_pic[j][i] = " ";
}
}
printf("\n");
printf("——以下是依據爬行密度得出的二維正太分佈圖的切面圖——");
printf("\n");
printf("\n");
for (int i = 0; i < highest; i++)
{
for (int j = 0; j < max_height; j++)
{
printf("%6s", map_pic[i][j]);
}
printf("\n");
}
return highest;
}
int main(int argc, char* argv[])
{
int max_height ;
int max_length ;
int loop_times ;
do
{
//輸入資料
printf("1.請輸入磚塊矩陣的行數(請輸入小於等於20的整數):");
scanf("%d", &max_height);
printf("2.請輸入磚塊矩陣的列數(請輸入小於等於20的整數):");
scanf("%d", &max_length);
printf("3.請輸入蟑螂的前行次數:");
scanf("%d", &loop_times);
printf("\n");
printf("\n");
Walk_data_list pic;
for (int k = 0; k < max_height; k++)
{
for (int j = 0; j < max_length; j++)
{
pic.count[k][j] = 0;
}
}
//執行函式
Send_data_list back_data;
Send_data_list continues_data;
int zhengtai_map;
back_data = Crawl_all(pic, max_height, max_length, loop_times);
int times = back_data.times_count;
int mark = back_data.mark;
int m = back_data.m;
int n = back_data.n;
int get_order;
for (int k = 0; k < max_height; k++)
{
for (int j = 0; j < max_length; j++)
{
pic.count[k][j] = back_data.end[k][j];
}
}
if (times <= loop_times && mark == 0)
{
printf("—————以下是蟑螂第一次爬完所有磚塊的資料—————\n");
printf("\n");
for (int k = 0; k < max_height; k++)
{
for (int j = 0; j < max_length; j++)
{
printf("%3d", back_data.first[k][j]);
}
printf("\n");
}
printf("\n");
printf("\n");
printf("—————以下是蟑螂爬行輸入要求次數後的資料—————\n");
printf("\n");
for (int k = 0; k < max_height; k++)
{
for (int j = 0; j < max_length; j++)
{
printf("%3d", back_data.end[k][j]);
}
printf("\n");
}
printf("\n");
printf("蟑螂完成所有磚塊的爬行一共進行了%d次\n", times);
printf("\n");
printf("\n");
printf("4.輸入4則繼續獲取完成要求次數後的大致正太分佈圖\n");
printf("5.輸入5則重新開始執行此程式\n");
printf("6.輸入6則退出程式\n");
printf("\n");
scanf("%d", &get_order);
if (get_order == 4)
{
zhengtai_map = Draw_zhengtai(pic, max_height, max_length);
//下面是對圖的輸出
printf("\n");
printf("正態切面列的最大值為:%d\n", zhengtai_map);
printf("\n");
}
if (get_order == 5)
{
continue;
}
if (get_order == 6)
{
return;
}
}
else
{
printf("—————以下是蟑螂爬行輸入要求次數後的資料—————\n");
printf("\n");
for (int k = 0; k < max_height; k++)
{
for (int j = 0; j < max_length; j++)
{
printf("%3d", back_data.end[k][j]);
}
printf("\n");
}
printf("\n");
printf("共進行了%d次爬行,但沒有完成全部磚塊的遍歷\n", loop_times);
printf("\n");
printf("\n");
printf("4.輸入4則繼續爬行直到完成一次遍歷\n");
printf("5.輸入5則繼續獲取完成要求次數後的大致正態分佈圖\n");
printf("6.輸入6則重新開始執行此程式\n");
printf("7.輸入7則退出程式\n");
printf("\n");
scanf("%d", &get_order);
if (get_order == 4)
{
continues_data = Crawl_rest(pic, max_height, max_length, m, n, loop_times);
int continues_times = 0;
continues_times = continues_data.times_count;
printf("\n");
printf("—————以下是蟑螂第一次爬完所有磚塊的資料—————\n");
printf("\n");
for (int k = 0; k < max_height; k++)
{
for (int j = 0; j < max_length; j++)
{
printf("%3d", continues_data.end[k][j]);
}
printf("\n");
}
printf("\n");
printf("蟑螂繼續進行%d次爬行完成所有磚塊,則一共進行了%d次\n", continues_times - loop_times, continues_times);
printf("\n");
}
if (get_order == 5)
{
zhengtai_map = Draw_zhengtai(pic, max_height, max_length);
//下面是對圖的輸出
printf("\n");
printf("正態切面列的最大值為:%d\n", zhengtai_map);
printf("\n");
}
if (get_order == 6)
{
continue;
}
if (get_order == 7)
{
return;
}
}
}while (1);
system("pause");
}