O( n )の実行時間としての次の最適解は、3 つ以上の連続したシーケンスがあるかどうかだけでなく、
問題の解決策は、グラフ理論を使用するのではなく、配列に関する知識を使用し、配列がメモリにどのように配置されているかについての知識を活用するだけで、非常に効率的かつ簡単に解決できます。次の C プログラムは、原理を示しています。
static inline void TrackLength(int **hpnCountPtr, int *pSeqStart)
{
(*((*hpnCountPtr) ? (*hpnCountPtr) : (*hpnCountPtr = pSeqStart)))++;
}
static int FindSequences(char *pcbGrid, int nWidth, int nHeight)
{
int nColIndex, nRowIndex, nSeqTotal;
int *panResult, *pnHorzSeq, *panVTable;
int **panVertSeqs;
size_t nResultLen;
char *pcbCheckPtr;
nResultLen = (size_t) (2 * nWidth * nHeight) * sizeof(int);
panResult = (int*) memset(alloca(nResultLen), 0, nResultLen);
panVTable = panResult + (nWidth * nHeight);
panVertSeqs = (int**) alloca(nWidth * sizeof(int*));
for (nColIndex = 0; nColIndex < nWidth; panVertSeqs[nColIndex++] = (int*) NULL);
for (pcbCheckPtr = pcbGrid, nRowIndex = 0; nRowIndex < nHeight; nRowIndex++)
for (pnHorzSeq = (int*) NULL, nColIndex = 0; nColIndex < nWidth; nColIndex++, pcbCheckPtr++)
{
if (*pcbCheckPtr != 'X')
{
pnHorzSeq = panVertSeqs[nColIndex] = (int*) NULL;
continue;
}
TrackLength(&pnHorzSeq, panResult + (pcbCheckPtr - pcbGrid));
TrackLength(panVertSeqs + nColIndex, panVTable + (pcbCheckPtr - pcbGrid));
}
/* Insert Debug Information Here */
for (nSeqTotal = 0, nColIndex = nWidth * nHeight; nColIndex; nColIndex--)
if (*panResult++ > 1)
nSeqTotal++;
return nSeqTotal;
}
重要なのは、対角線ではなく、水平方向と垂直方向のシーケンスのみを見つける必要があるという要件でした。2D 配列であるということは、2D 配列が最初から最後まで 1 回のパスで処理できる単純なフラットで連続したメモリ ブロックであることを理解することで、大幅な最適化が可能になることを意味します。
メモリ配置を利用して、 である連続するメモリ位置ごとにインクリメントされる単一のカウンターのみを使用して水平シーケンスを追跡しX
、非が検出されるたびにゼロにリセットできますX
。配列の 2D ビューのすべての「行」の最後でカウンターが強制的にリセットされると、水平方向のシーケンスが正常に追跡されます。
水平方向のシーケンスが追跡されると同時に、垂直方向のシーケンスも同時に追跡できます。ただし、配列内の列内の隣接するセルはwidth
バイト間隔で表示されるwidth
ため、列ごとに 1 つのシーケンスで行をスキャンするときに、行内で可能なすべての垂直シーケンスを追跡するカウンターの配列を保持する必要があります。
最後に、私のソリューションの本当の美しさは、カウンターが実際には 2 つの 2D 配列 (1 つは垂直シーケンスの追跡用、もう 1 つは水平シーケンスの追跡用) へのポインターであることです。各配列の各位置は、シーケンスの潜在的な開始を表し、関数が完了すると、各配列には、その位置から始まる各シーケンスの長さが各位置に含まれます。
これは、次の図を使用して最もよく示されています。
Grid Horizontal Vertical
Sequences Sequences
....X...X. 0000100010 0000300030
.X..X...X. 0100100010 0300000000
.XXXX..XXX 0400000300 0011000201
.X.....X.. 0100000100 0000000000
Horizontal Sequences 配列の各桁は、その位置から始まる水平シーケンスの長さを表します。垂直シーケンスの垂直シーケンス配列についても同じことが言えます。
プログラムは入力配列を 1 つ、正確に 1 つ通過させ、各配列要素の処理時間は一定であるため、このアルゴリズムは O( n ) で実行され、任意のサイズのグリッドに最適です。
上記の関数を以下のプログラムに挿入すると、次のようになります。
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <alloca.h>
#define GRID_WIDTH 18
#define GRID_HEIGHT 9
static char *s_szGrid =
"X X X X X X X X X "
" X X X X X X X X X"
"X X X X X X X X X "
" X X X X X X X X X"
"XXX X X X X X X X "
" X X X X X X X X X"
"XXXXX X X X X X X "
" X X X X X X X X X"
"X X XXX X X X X X ";
void main(void)
{
int nSeqTotal = FindSequences(s_szGrid, GRID_WIDTH, GRID_HEIGHT);
printf("\nGrid has %i contiguous sequences\n", nSeqTotal);
if (nSeqTotal)
printf(">>> %sone sequence detected\n", (nSeqTotal > 1) ? "more than " : "");
}
/* Insert Debug Information Here */
そして、次のデバッグ コードのチャンクを、コメントが表示されている関数に挿入します。
{
int nXIndex, nYIndex;
int *pnValue;
printf("Horizontal:\n");
for (pnValue = panResult, nYIndex = 0; nYIndex < nHeight; nYIndex++, printf("\n"))
for (nXIndex = 0; nXIndex < nWidth; nXIndex++, printf("%2i", *pnValue++));
printf("\nVertical:\n");
for (nYIndex = 0; nYIndex < nHeight; nYIndex++, printf("\n"))
for (nXIndex = 0; nXIndex < nWidth; nXIndex++, printf("%2i", *pnValue++));
}
次に、プログラムは次の出力を生成します。
Horizontal:
1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
3 0 0 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
5 0 0 0 0 0 1 0 1 0 1 0 1 0 1 0 1 0
0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
1 0 1 0 3 0 0 0 1 0 1 0 1 0 1 0 1 0
Vertical:
1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
0 5 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
0 0 0 3 0 1 0 1 0 1 0 1 0 1 0 1 0 1
1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
0 0 0 0 0 2 0 1 0 1 0 1 0 1 0 1 0 1
1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
Grid has 6 contiguous sequences
>>> more than one sequence detected
ただし、入力グリッドが次のように変更された場合:
static char *s_szGrid =
"X X X X X X X X X "
" X X X X X X X X X"
"X X X X X X X X X "
" X X X X X X X X X"
"X X X X X X X X X "
" X X X X X X X X X"
"X X X X X X X X X "
" X X X X X X X X "
"X X X X X X X X XX";
出力は次のようになります。
Horizontal:
1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 0
1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 2 0
Vertical:
1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 0
1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 1
Grid has 1 contiguous sequences
>>> one sequence detected