Проблема в B-дереве (не B+/B*), реализованная с файлами на C

я новичок здесь и прежде всего, я хочу извиниться, если я делаю ошибки в вопросе. Моя проблема: я хочу реализовать B-дерево в C, используя файл для хранения дерева... моя программа читает 10000 строк по 10 символов в каждой из текстового файла и сохраняет это содержимое в двоичном файле .DAT, организовано через B-дерево; то пользователь может искать строку.

Я использую алгоритмы "Cormen, et al. - Introduction to Algorithms (3ed)", которые кажутся правильными, понятными и функциональными. НО, моя программа просто получает ошибки времени выполнения... такие как Segmentation Fault и Infinite Loop. Я пытался отладить в течение 5 дней, но безуспешно! Функции B-дерева рекурсивны, что я лично ненавижу... я думаю, что именно рекурсия делает отладку такой сложной!

Мой код относительно большой, и он разделен на 2 файла, один источник, один заголовок. Я просто опубликую здесь функции B-дерева и объявления переменных. Но, если кто-то хочет увидеть полный код, я опубликую ссылку «iFile.it» (разрешено ли это? Извините, если нет!)...

Большое спасибо за внимание и помощь... извините за большой вопрос!

Ссылка на источник [не так важно, просто main()]: http://ifile.it/n73drmc/b-tree_file.c

Ссылка на заголовок [все функции здесь]: http://ifile.it/u1fa3kp/b-tree_file.h

Текстовый файл со строками: http://ifile.it/7hu95ot/arq_5.txt

ПРИМЕЧАНИЯ о коде:

i) Free() ведут себя очень странно... поэтому я прокомментировал их, потому что, если я их использую, моя программа будет глючить еще больше!

ii) Все комментарии к коду являются моими идеями, чтобы попытаться решить проблемы, и следует учитывать, что я пробовал их.

iii) Я носитель португальского языка, поэтому функции и переменные могут иметь странные имена для носителей английского языка... извините за это!

КОД:

 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <ctype.h>

 //defs of pre-compiler

 #ifdef __linux
    #define PAUSA "read p"
    #define CLRSCR "clear"
 #else
    #define PAUSA "Pause"
    #define CLRSCR "cls"
 #endif

 #define T 5 // minimum degree of B-tree
 #define Min (T-1)
 #define Max ((2*T)-1)
 //

 //global vars
 FILE *arqt, *arqb;

 char VAL[11];
 long int lt = 0;

 typedef struct {
    unsigned int num;
    long int pos;
    char chave[(Max+1)][11]; //chave in portuguese = key in english!
    short int folha; //folha in portuguese = leaf in english!
    long int c[(Max+2)];
 } Nod;

 Nod *raiz = NULL; //raiz in portuguese = root in english!
 //

 void b_split(Nod *x, unsigned int i, Nod *y) { //B-TREE-SPLIT-CHILD //that function split a B-tree node
Nod *z = NULL;
unsigned int j=1;

z = (Nod*)realloc(z,sizeof(Nod));

fseek(arqb,0,SEEK_END);
z->pos = ftell(arqb);

z->folha = y->folha;
z->num = Min;

for(j=1;j<=Min;j++) {strcpy(z->chave[j],y->chave[j+T]);}

if (y->folha == 0) {
    for(j=1;j<=T;j++) {z->c[j] = y->c[j+T];}

}

y->num = Min;

for(j=(x->num + 1);j<=(i+1);j--) {x->c[(j+1)] = x->c[j];}

x->c[(i+1)] = z->pos;

for(j=x->num;j<=i;j--) { strcpy(x->chave[j+1],x->chave[j]); }

strcpy(x->chave[i],y->chave[T]);

x->num = x->num + 1;

fseek(arqb,x->pos,SEEK_SET);
fwrite(x,sizeof(Nod),1,arqb);

fseek(arqb,y->pos,SEEK_SET);
fwrite(y,sizeof(Nod),1,arqb);

fseek(arqb,z->pos,SEEK_SET);
fwrite(z,sizeof(Nod),1,arqb);

//free(z);
//free(y);
}

void b_ins(Nod *x, char *val) { //B-TREE-INSERT-NONFULL //insert a key in nonfull node
unsigned int i=0;
Nod *C = NULL;

i = x->num;

if (x->folha == 1) {
    while ( (i >= 1) && (strcmp(val,x->chave[i]) < 0) ) {
        strcpy(x->chave[(i+1)],x->chave[i]);
        i--;
    }
    strcpy(x->chave[(i+1)],val);
    x->num = x->num + 1;

    fseek(arqb,x->pos,SEEK_SET);
    fwrite(x,sizeof(Nod),1,arqb);

}

else {
    while ( (i >= 1) && (strcmp(val,x->chave[i]) < 0) ) {i--;}
    i++;

    C = (Nod*)realloc(C,sizeof(Nod));

    fseek(arqb,x->c[i],SEEK_SET);
    fread(C,sizeof(Nod),1,arqb);

    if (C->num == Max) {
        b_split(x,i,C);

        if ( strcmp(val,x->chave[i]) > 0 ) {i++;}

    }
    fseek(arqb,x->c[i],SEEK_SET);
    fread(C,sizeof(Nod),1,arqb);

    b_ins(C,val);

    //free(C);
}

}

void insere_b(char *val) { //B-TREE-INSERT //i believe the problem is here!
Nod *S = NULL,*R = NULL;

R = (Nod*)realloc(R,sizeof(Nod));
R = raiz;

if (R->num == Max) {
    S = (Nod*)realloc(S,sizeof(Nod));

/*      fseek(arqb,0,SEEK_END);
        S->pos = ftell(arqb);
*/
    raiz = S;

    S->folha = 0;
    S->num = 0;
    S->c[1] = R->pos;

 /*      fseek(arqb,S->pos,SEEK_SET);
         fwrite(S,sizeof(Nod),1,arqb);

 */
    b_split(S,1,R);
    b_ins(S,val);

    //free(S);
}

else {b_ins(R,val);}

//free(R);
}

void busca_b(Nod *x, char *val) { //B-TREE-SEARCH //self explanatory
unsigned int i=1;
Nod *C = NULL;

while( (i <= x->num) && ( strcmp(val, x->chave[i]) > 0 ) ) {i++;}

if ( (i <= x->num) && ( strcmp(val, x->chave[i]) == 0 ) ) {printf ("\nValor encontrado!\n");}

else {
    if (x->folha == 1) {printf ("\nValor NAO encontrado!\n");}

    else {
        C = (Nod*)realloc(C,sizeof(Nod));
        fseek(arqb,x->c[i],SEEK_SET);
        fread(C,sizeof(Nod),1,arqb);

        lt++;

        busca_b(C,val);
        //free(C);
    }

}

}

void cria_b() { // cria arvore B //create the B-tree
long int i = 0;
char V[11];

raiz->folha = 1;
raiz->num = 0;
raiz->pos = 0;
for (i=1;i<=(Max+1);i++) {raiz->c[i] = -1;}

fseek(arqb,raiz->pos,SEEK_SET);
fwrite(raiz,sizeof(Nod),1,arqb);

rewind(arqt);
for (i=0;i<fSize(arqt);i++) {
    fscanf(arqt,"%s\n",V);
    insere_b(V);
}
}

person guipy    schedule 16.04.2011    source источник


Ответы (2)


Код немного сложно читать, но вот что я вижу:

R = (Nod*)realloc(R,sizeof(Nod));
R = raiz;

Вы делаете выделение памяти и сразу выбрасываете результат. Результат realloc может отличаться от исходного указателя.

Вы, вероятно, хотите, чтобы функция инициализировала узлы вашего дерева, чтобы сделать это более понятным - мне трудно следовать коду.

Я ожидаю увидеть указатели в каждом узле дерева, но я их не вижу. Я не слежу за вашей реализацией. Я бы предложил нарисовать ваше дерево на бумаге со стрелками, показывающими, что указывает на что, и рассмотреть все угловые случаи (например, первая вставка, когда дерево пусто), поскольку это обычно то, на чем люди могут застрять. Также просмотрите свой код с помощью отладчика и посмотрите, ведет ли он себя так, как вы ожидаете.

РЕДАКТИРОВАТЬ: Если все дерево построено в файле (что, по-видимому, является намерением, глядя на это немного больше), вам, вероятно, вообще не нужно выполнять какое-либо динамическое выделение памяти.

В общем случае вы хотите:

  • Следите за переполнением, например. вы пишете в ячейку памяти за пределами того, что доступно/было выделено, строковые функции восприимчивы к ним, убедитесь, что у вас всегда есть допустимая строка с нулевым завершением, где это требуется, и у вас всегда есть достаточно места для вашей строки (то есть это не дольше, чем вы можете держать).
  • Проверьте возвращаемые значения NULL из функций выделения памяти.
  • Убедитесь, что вы не пропускаете память, т.е. выделяете и выбрасываете указатель, никогда не вызывая free().
  • Убедитесь, что вы всегда вызываете free с ранее выделенным указателем и не используете этот указатель в какой-либо более поздней точке.
  • Кажется, есть много мест, где вы делаете realloc(), но действительно хотите использовать malloc().

Некоторые компиляторы C/C++ могут выполнять дополнительные проверки во время выполнения (например, Visual Studio), которые могут быть полезны для выявления проблемы.

Также обратите внимание на хорошие комментарии Джонатана относительно условностей и стиля.

person Guy Sirton    schedule 17.04.2011
comment
Большое спасибо за ваш комментарий, парень... я не понимаю, что такое realloc... не могли бы вы объяснить мне больше? Насчет выделения памяти, я думаю как и вы.. возможно, я мог бы сделать эту программу без какого-либо указателя, кроме файлов.. вы согласны с этим? - person guipy; 17.04.2011
comment
realloc во многих случаях ведет себя как malloc, или я ошибаюсь? Например, когда указатель не инициализирован, если использовать realloc или malloc, результат будет другим? - person guipy; 17.04.2011
comment
@guipy: Если вы передаете NULL в realloc, он ведет себя как malloc, но сбивает с толку читателя, потому что я ожидаю, что realloc будет использоваться для изменения размера выделенной памяти, а не для выделения фиксированного количества байтов. Да вроде указатели и не нужны... - person Guy Sirton; 17.04.2011

Файлы заголовков должны содержать только объявления структур, определения типов, перечисления, определения и объявления функций. Возможно, у вас есть встроенные определения функций, но, вероятно, нет.

В вашем заголовочном файле есть полные функции - те, которые вряд ли будут встроены и не объявлены встроенными. Это означает, что он непригоден для использования по прямому назначению — для предоставления объявлений файлам с использованием кода, определенного в соответствующем файле C.

Код нелегко читать, и проблема не в языке. Такие конструкции, как:

while ( (i >= 1) && (strcmp(val,x->chave[i]) < 0) ) {i--;}

не являются идиоматическими C. Обычный способ написания:

while (i >= 1 && strcmp(val, x->chave[i]) < 0)
    i--;

Существуют рекомендации по кодированию, которые требуют фигурных скобок вокруг всех тел циклов и условных выражений; если вы страдаете от одного из них, то вы используете одно из нескольких соглашений о кодировании:

while (i >= 1 && strcmp(val, x->chave[i]) < 0)
{
    i--;
}

while (i >= 1 && strcmp(val, x->chave[i]) < 0) {
    i--;
}

Я решительно предпочитаю первое; есть много людей, которые предпочитают последнее.

В этом мире есть системы, отличные от Windows и Linux.

//defs of pre-compiler
#ifdef __linux
    #define PAUSA "read p"
    #define CLRSCR "clear"
#else
    #define PAUSA "Pause"
    #define CLRSCR "cls"
#endif

Это не совсем разумно работает для моей среды — MacOS X. Я не особо увлекаюсь программами, использующими системные операторы, но я думаю, что проблема в том, что я старый дурак, который не использует и IDE (хотя один одна из причин, по которой я не использую IDE, заключается в том, что программы командной строки не работают в них нормально, а я в основном пишу программы командной строки, поэтому они враждебны моему обычному режиму работы).


Одна из основных ошибок производительности:

for (i = 0; i < fSize(arqt); i++)

Это вызывает функцию fSize() на каждой итерации, и функция проходит по всему файлу, определяя, сколько строк в файле, восстанавливая позицию чтения перед возвратом. Неясно, действительно ли вам нужно считать количество строк — вы, вероятно, можете просто читать строки по ходу дела. Однако, если вам нужно посчитать строки, сделайте это только один раз.

int lines = fSize(arqt);
for (i = 0; i < lines; i++)

Есть ряд мест, где у вас есть циклы, использующие:

for (x = 1; x <= MAX; x++)

Обычно это неверно в C; индексы массива начинаются с нуля, а каноническая форма цикла такова:

for (x = 0; x < MAX; x++)

Стилистически вы обычно резервируете все имена в верхнем регистре для значений #define или enum, а не для обычных переменных.


В функции insere_b() у вас есть:

//B-TREE-INSERT //i believe the problem is here!
void insere_b(char *val)
{
    Nod *S = NULL, *R = NULL;

    R = (Nod*)realloc(R, sizeof(Nod));
    //R = raiz;

    if (R->num == Max)
    {
        S = (Nod*)realloc(S, sizeof(Nod));

Кто-то еще указал, что строка R = raiz; сомнительна. Вы присваиваете null R; то вы realloc() его. Это всегда эквивалентно malloc(). Кроме того, выделенная память не инициализируется, поэтому вы получаете случайные значения для игры. Это приводит к проблемам.

Затем вы выполняете аналогичную последовательность с S (выделение памяти через realloc()), хотя на этот раз вы последовательно инициализируете части (возможно, все) выделенной структуры.

Этого достаточно, чтобы вызвать проблемы.


Когда код входит в b_split() в первый раз, вы сталкиваетесь с проблемой со значениями позиции. В частности, y->pos равно нулю, но x->pos тоже, поэтому программа сохраняет (записывает) данные на два узла с одинаковым смещением, что редко является рецептом счастья. Так как x является корневым узлом (по крайней мере, в этом контексте - первое разделение), он должен быть в позиции 0; оба y и z должны быть в разных позициях. Узел y также содержит ненулевые ключи, хотя это не имеет большого значения (за исключением целей аккуратности), поскольку значение y->num указывает, что они не используются.

Вы также никогда не используете значение ключа chave[0], AFAICT. Это немного расточительно.

person Jonathan Leffler    schedule 17.04.2011
comment
Привет, Джонатан, я ценю твои предложения и знаю, что ты имеешь в виду под идиоматической C. В моем случае я просто как ... любитель, поэтому я пишу так, как предпочитаю. В частности, я предпочитаю while( (cond1) && (cond2)) {code}... именно так, как думает мой разум, хе-хе... По поводу последнего комментария: моя программа будет работать только в Linux и Dos, так что... Mac OS Х меня не касается! В любом случае, у вас есть какие-либо ошибки, чтобы указать мне? - person guipy; 17.04.2011
comment
Вау, спасибо большое!! Я согласен с вами в случае с fSize... вызывать функцию каждый раз - пустая трата времени! MAX и MIN, а также T являются константами, определяемыми #define. Мои массивы работают от 0 до K + 1, потому что мои алгоритмы от 1 до K, поэтому я сохраняю их, теряя первый элемент массивов. Указатели опасны, сейчас попробую эту программу без них! Если запустится, скажу здесь... еще раз спасибо! - person guipy; 17.04.2011
comment
@guipy: В моем полурабочем коде я заменил строку realloc() в insereb() на Nod *R = raiz;. - person Jonathan Leffler; 17.04.2011
comment
@ Джонатан: почему полурабочий? Есть баги указателей, или просто ошибки в B-дереве? Еще раз спасибо за вашу помощь... я пытаюсь без указателей без успеха... - person guipy; 17.04.2011
comment
@guipy: ну, полуработает, потому что b_split() не работает должным образом. Но чтобы зайти так далеко, я изменил insere_b(), как указано. Я сделал (большое) количество других модификаций. Свяжитесь со мной по электронной почте, если вам нужен мой отредактированный код (см. Мой профиль) - но измененный код не является панацеей, потому что он все еще имеет сломанный b_split() по крайней мере. Я также не понял, как отслеживать все узлы B-Tree в вашей программе; Я столкнулся с проблемами, как только вы добавили второй (и третий) узлы. - person Jonathan Leffler; 17.04.2011
comment
@Jonathan: проблема действительно начинается с b_split ... если вы просто добавите ключи в корень, все будет работать хорошо. Чтобы отслеживать узел, вам нужно Node-›pos [позиция в файле]... если вы хотите узнать сыновей, используйте указатели Node-›c[i], i от 1 до Max+1 (Max — максимальное число ключей на узел)... отец узла неизвестен - person guipy; 17.04.2011