我必须使用矩阵链接列表读取字母的文本文件,其中每个字母周围必须有8个指针。
这是我要做的: enter image description here
文本文件是这样的:
JDCPCPXOAA
ZXVOVXFRVV
NDLEIRBIEA
YTRQOMOIIO
FZZAPXERTQ
XAUEOEOOTO
PORTUOAZLZ
CZNOQUPUOP
在我的代码中,我只能阅读第一行中的字母。
有人可以帮我吗?
typedef struct letter ///estrutura para cada letra da sopa
{
char *lname;
struct letter *pnext;
}LETTER;
typedef struct soup ///estrutura para a sopa de letras
{
int lin;
int col;
LETTER *pfirst;
}SOUP;
void read_soup_txt(SOUP *pcs,char *fn,int lin,int col)
{
FILE *fp;
fp=fopen(fn,"r");
char c;
if(fp!=NULL)
{
pcs->lin=lin;
pcs->col=col;
LETTER *current=malloc(sizeof(LETTER)),*previous;
pcs->pfirst=current;
for(int i=0;i<pcs->lin;i++) ///linhas
{
for(int j=0;j<pcs->col;j++) ///colunas
{
fscanf(fp,"%c",&c); ///le o char
current->lname=malloc(sizeof(char)); ///aloca espaço para o char
strcpy(current->lname,&c); ///copia o char para a estrutura
previous=current;
if(i==pcs->lin-1)
{
current=NULL;
}
else
current=malloc(sizeof(LETTER));
previous->pnext=current;
}
}
}
else
printf("Erro ao abrir arquivo!");
fclose(fp);
}
答案 0 :(得分:1)
每个字母周围必须有8个指针。
这意味着您的字母结构应类似于
struct letter {
struct letter *up_left;
struct letter *up;
struct letter *up_right;
struct letter *left;
struct letter *right;
struct letter *down_left;
struct letter *down;
struct letter *down_right;
int letter;
};
您也不需要字母汤。因为您按顺序读取字符,所以可以将它们直接读取到图形中。诀窍在于,您需要保持一个struct letter
指针指向图中左上角的字母;一个struct letter
指针,指向每一行的第一个字母;每个添加的新字母都有一个struct letter
指针。
这是pseudocode中的逻辑:
Function ReadGraph(input):
Let topleft = NULL # Top left letter in the graph
Let leftmost = NULL # Leftmost letter in current line
Let previous = NULL # Previous letter in current line
Let current = NULL # Current letter
Let letter = ''
Do:
Read next letter from input
While (letter is not a letter nor EOF)
If letter is EOF:
# No letters at all in the input, so no graph either.
Return NULL
End If
topleft = new struct letter (letter)
leftmost = topleft
current = topleft
# Row loop. First letter is already in current.
Loop:
# Loop over letters in the current line
Loop:
Read new letter from input
If letter is EOF, or newline:
Break
End If
previous = current
current = new struct letter
current->left = previous
previous->right = current
If current->left->up is not NULL:
current->up_left = current->left->up
current->up_left->down_right = current
If current->up_left->right is not NULL:
current->up = current->up_left->right
current->up->down = current
If current->up->right is not NULL:
current->up_right = current->up->right
current->up_right->down_left = current
End If
End If
End If
End Loop
If letter is not EOF:
While (letter is not EOF) and (letter is not a letter):
Read new letter from input
End While
End If
If letter is EOF:
Break
End If
# We have a first letter on a new line.
current = new letter structure
current->up = leftmost
leftmost->down = current
If current->up->right is not NULL:
current->up_right = current->up->right
current->up_right->down_left = current
End If
leftmost = current
End Loop
Return topleft
End Function
请注意如何对输入流中的第一个字符进行不同的处理(从一开始),以及如何对每个后续行中的第一个字符进行不同的处理(在函数结尾附近)。从逻辑上或结构上来说,这可能感觉很奇怪,但是这样做可以使代码保持简单。
还请注意如何构建双向链接。因为我们是从上到下,从左到右阅读的,所以我们先建立链接,然后是左起,然后是左起,然后是上,然后是右起;反向链接紧接在向前链接之后。
这需要一些思考,以了解其工作原理。考虑:
up_left │ up │ up_right
──────────┼──────┼───────────
left │ curr │ right
──────────┼──────┼───────────
down_left │ down │ down_right
构造curr
时,我们知道left
是否存在,因为我们分别处理每行的第一个字母。
如果curr->left
为非NULL,并且curr->left->up
为非NULL,则我们知道有前一行,我们可以指向curr->up_left
指向它。当然,它的->down_right
应该指向curr
,以使链接保持一致。
如果curr->up_left
为非NULL,而curr->up_left->right
为非NULL,则我们知道上一行在同一列中有一个字母。我们可以将curr->up
设置为指向它,而将其->down
设置为指向curr
。
如果curr->up
为非NULL,并且curr->up->right
为非NULL,则我们知道上一行在下一列中有一个字母。我们可以将curr->up_right
设置为指向它,而将其->down_left
设置为指向curr
。
现在,因为我们从左到右读取每一行,所以每一行的所有列都被填充到最右边的列。如果继续使用上述逻辑,您将发现第二行填充了从第一行字母到第二行字母的其余链接,依此类推。
这还意味着,如果输入文件包含特殊字符,例如非字母节点的'*'
,则应在构造图形时创建它们,就像它们是普通字母一样,以确保上述逻辑链接作品。
在读取整个图之后,您可以从图中一一删除那些非字母节点。要删除节点,请首先将其的反向链接(从其相邻字母开始)设置为NULL,然后free()
。
在free()
对其进行结构化之前,我个人对结构进行了“毒化”,将letter
设置为已知的不可能值(WEOF
,用于宽输入范围),以及所有指向NULL
,这样一来,如果其他代码在释放后使用了该结构(例如,在释放错误后使用 ),例如,因为它以某种方式缓存了指针,则更容易检测。
(当您free()
指针时,C库通常不会立即将其返回给操作系统或将其清除;通常,动态分配的区域只会添加到内部可用堆中,因此将来的分配只能重用该内存。不幸的是,这意味着如果您不“释放”释放的结构,有时它们以后仍然可以访问。这种“使用后释放”的错误非常令人讨厌,绝对值得“不必要的”使用工作”,只是为了调试结构而使结构中毒。)
为促进中毒,并在某些情况下(如果不必要地降低了速度)使中毒变得容易,最好使用辅助函数来创建和破坏结构:
static inline struct letter *new_letter(const int letter)
{
struct letter *one;
one = malloc(sizeof *one);
if (!one) {
fprintf(stderr, "new_letter(): Out of memory.\n");
exit(EXIT_FAILURE);
}
one->up_left = NULL;
one->up = NULL;
one->up_right = NULL;
one->left = NULL;
one->right = NULL;
one->down_left = NULL;
one->down = NULL;
one->down_right = NULL;
one->letter = letter;
return one;
}
static inline void free_letter(struct letter *one)
{
if (one) {
one->up_left = NULL;
one->up = NULL;
one->up_right = NULL;
one->left = NULL;
one->right = NULL;
one->down_left = NULL;
one->down = NULL;
one->down_right = NULL;
one->letter = EOF;
free(one);
}
}
我通常在定义struct letter
的头文件中包含这些函数;因为它们是微小的类似于宏的函数,所以将它们标记为static inline
,告诉C编译器它们只需要在同一编译单元中就可以访问,并且不需要生成函数并调用这些函数。函数,但可以将代码内联到调用它们的位置。
我个人使用以下代码编写并验证了上述伪代码
#include <stdlib.h>
#include <locale.h>
#include <wchar.h>
#include <stdio.h>
struct letter {
struct letter *chain; /* Internal chain of all known letters */
struct letter *up_left;
struct letter *up;
struct letter *up_right;
struct letter *left;
struct letter *right;
struct letter *down_left;
struct letter *down;
struct letter *down_right;
wint_t letter;
};
static struct letter *all_letters = NULL;
struct letter *new_letter(wint_t letter)
{
struct letter *one;
one = malloc(sizeof *one);
if (!one) {
fprintf(stderr, "new_letter(): Out of memory.\n");
exit(EXIT_FAILURE);
}
one->letter = letter;
one->chain = all_letters;
all_letters = one;
one->up_left = NULL;
one->up = NULL;
one->up_right = NULL;
one->left = NULL;
one->right = NULL;
one->down_left = NULL;
one->down = NULL;
one->down_right = NULL;
return one;
}
我更喜欢使用宽输入,因为在兼容的操作系统中,您可以使用任何将您的语言环境视为字母的字形,而不仅仅是ASCII A-Z。您所需要做的就是拥有
if (!setlocale(LC_ALL, ""))
fprintf(stderr, "Warning: Current locale is not supported by your C library.\n");
if (fwide(stdin, 1) < 1)
fprintf(stderr, "Warning: Wide standard input is not supported by your C library for current locale.\n");
if (fwide(stdout, 1) < 1)
fprintf(stderr, "Warning: Wide standard output is not supported by your C library for current locale.\n");
在您的main()
开头,并使用广泛的I / O功能(fwprintf()
,fgetwc()
等),前提是您拥有标准的C环境。 (显然,某些Windows用户在Windows中对UTF-8支持存在问题。请向Microsoft投诉;以上行为是基于C标准的。)
chain
成员用于将所有创建的字母链接到一个链接列表中,因此我们可以使用下面的函数以Graphviz Dot语言绘制整个图形。 (Graphviz适用于所有操作系统,在我看来,这是开发或调试使用链表或图形的代码时的绝佳工具。)circo
实用程序似乎非常擅长绘制此类代码图。
int letter_graph(FILE *out)
{
struct letter *one;
/* Sanity check. */
if (!out || ferror(out))
return -1;
/* Wide output. */
if (fwide(out) < 1)
return -1;
fwprintf(out, L"digraph {\n");
for (one = all_letters; one != NULL; one = one->chain) {
fwprintf(out, L" \"%p\" [ label=\"%lc\" ];\n",
(void *)one, one->letter);
if (one->up_left)
fwprintf(out, L" \"%p\" -> \"%p\" [ label=\"↖\" ];\n",
(void *)one, (void *)(one->up_left));
if (one->up)
fwprintf(out, L" \"%p\" -> \"%p\" [ label=\"↑\" ];\n",
(void *)one, (void *)(one->up));
if (one->up_right)
fwprintf(out, L" \"%p\" -> \"%p\" [ label=\"↗\" ];\n",
(void *)one, (void *)(one->up_right));
if (one->left)
fwprintf(out, L" \"%p\" -> \"%p\" [ label=\"←\" ];\n",
(void *)one, (void *)(one->left));
if (one->right)
fwprintf(out, L" \"%p\" -> \"%p\" [ label=\"→\" ];\n",
(void *)one, (void *)(one->right));
if (one->down_left)
fwprintf(out, L" \"%p\" -> \"%p\" [ label=\"↙\" ];\n",
(void *)one, (void *)(one->down_left));
if (one->down)
fwprintf(out, L" \"%p\" -> \"%p\" [ label=\"↓\" ];\n",
(void *)one, (void *)(one->down));
if (one->down_right)
fwprintf(out, L" \"%p\" -> \"%p\" [ label=\"↘\" ];\n",
(void *)one, (void *)(one->down_right));
}
fwprintf(out, L"}\n");
return 0;
}
如果输入文件是
ABC
DEF
GHI
图形的点描述是
digraph {
"0x1c542f0" [ label="I" ];
"0x1c542f0" -> "0x1c54170" [ label="↖" ];
"0x1c542f0" -> "0x1c541d0" [ label="↑" ];
"0x1c542f0" -> "0x1c54290" [ label="←" ];
"0x1c54290" [ label="H" ];
"0x1c54290" -> "0x1c54110" [ label="↖" ];
"0x1c54290" -> "0x1c54170" [ label="↑" ];
"0x1c54290" -> "0x1c541d0" [ label="↗" ];
"0x1c54290" -> "0x1c54230" [ label="←" ];
"0x1c54290" -> "0x1c542f0" [ label="→" ];
"0x1c54230" [ label="G" ];
"0x1c54230" -> "0x1c54110" [ label="↑" ];
"0x1c54230" -> "0x1c54170" [ label="↗" ];
"0x1c54230" -> "0x1c54290" [ label="→" ];
"0x1c541d0" [ label="F" ];
"0x1c541d0" -> "0x1c54050" [ label="↖" ];
"0x1c541d0" -> "0x1c540b0" [ label="↑" ];
"0x1c541d0" -> "0x1c54170" [ label="←" ];
"0x1c541d0" -> "0x1c54290" [ label="↙" ];
"0x1c541d0" -> "0x1c542f0" [ label="↓" ];
"0x1c54170" [ label="E" ];
"0x1c54170" -> "0x1c53ff0" [ label="↖" ];
"0x1c54170" -> "0x1c54050" [ label="↑" ];
"0x1c54170" -> "0x1c540b0" [ label="↗" ];
"0x1c54170" -> "0x1c54110" [ label="←" ];
"0x1c54170" -> "0x1c541d0" [ label="→" ];
"0x1c54170" -> "0x1c54230" [ label="↙" ];
"0x1c54170" -> "0x1c54290" [ label="↓" ];
"0x1c54170" -> "0x1c542f0" [ label="↘" ];
"0x1c54110" [ label="D" ];
"0x1c54110" -> "0x1c53ff0" [ label="↑" ];
"0x1c54110" -> "0x1c54050" [ label="↗" ];
"0x1c54110" -> "0x1c54170" [ label="→" ];
"0x1c54110" -> "0x1c54230" [ label="↓" ];
"0x1c54110" -> "0x1c54290" [ label="↘" ];
"0x1c540b0" [ label="C" ];
"0x1c540b0" -> "0x1c54050" [ label="←" ];
"0x1c540b0" -> "0x1c54170" [ label="↙" ];
"0x1c540b0" -> "0x1c541d0" [ label="↓" ];
"0x1c54050" [ label="B" ];
"0x1c54050" -> "0x1c53ff0" [ label="←" ];
"0x1c54050" -> "0x1c540b0" [ label="→" ];
"0x1c54050" -> "0x1c54110" [ label="↙" ];
"0x1c54050" -> "0x1c54170" [ label="↓" ];
"0x1c54050" -> "0x1c541d0" [ label="↘" ];
"0x1c53ff0" [ label="A" ];
"0x1c53ff0" -> "0x1c54050" [ label="→" ];
"0x1c53ff0" -> "0x1c54110" [ label="↓" ];
"0x1c53ff0" -> "0x1c54170" [ label="↘" ];
}
(这是相反的顺序,因为我在链接列表的开头插入了每个新字母)。 circo
从中得出以下图形:
在开发过程中,我还要检查链接是否一致:
for (one = all_letters; one != NULL; one = one->chain) {
if (one->up_left && one->up_left->down_right != one)
fprintf(stderr, "'%c'->up_left is broken!\n", one->letter);
if (one->up && one->up->down != one)
fprintf(stderr, "'%c'->up is broken!\n", one->letter);
if (one->up_right && one->up_right->down_left != one)
fprintf(stderr, "'%c'->up_right is broken!\n", one->letter);
if (one->left && one->left->right != one)
fprintf(stderr, "'%c'->left is broken!\n", one->letter);
if (one->right && one->right->left != one)
fprintf(stderr, "'%c'->right is broken!\n", one->letter);
if (one->down_left && one->down_left->up_right != one)
fprintf(stderr, "'%c'->down_left is broken!\n", one->letter);
if (one->down && one->down->up != one)
fprintf(stderr, "'%c'->down is broken!\n", one->letter);
if (one->down_right && one->down_right->up_left != one)
fprintf(stderr, "'%c'->down_right is broken!\n", one->letter);
}
通过一致的链接,我的意思是如果为a->left == b
,则为b->right == a
。当然,支票无法判断a->left
或b->right
是否错误;它只能检测它们是否一致。