0

私は C の初心者で、K&R の本 The C Programming Language から学んでいます。バイナリ ツリーの演習を行った後、char*、long、および double のバイナリ ツリーのヘッダーを作成したいと考えました。

次のコードには、私を悲しませている関数があります。文字ポインタの配列に、辞書順でツリーに格納された値を入力する必要がありますが、どこかにバグがあります。文字列ツリー ヘッダー btree.h のコードは次のとおりです。

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

    /************** TYPES **************/
    typedef struct ctree 
    {
        char *name;
        ctree *left;
        ctree *right;
    }; 
    /************** Globals **************/

    static int c_inc = 0;

    /************** Function Prototypes **************/

    ctree *add_to_c_tree  (ctree *cnode, char *name);
    void print_c_tree     (ctree  *cnode);
    ctree *c_tree_alloc   (void);
    void   c_tree_free    (ctree  *cnode);
    void  return_c_tree   (ctree  *cnode, char **array);

    /************** Function Definitions **************/

    /* add_to_c_tree() : Adds a new node to a *character binary tree */
    ctree *add_to_c_tree (ctree *cnode, char *name){

        /* If the node is null, allocate memory for it, 
         * copy the name and set the internal nodes to null*/
        if(cnode == NULL){
            cnode = c_tree_alloc();
            cnode->name = strdup(name); 
            cnode->left = cnode->right = NULL;
        }
        /* If initialised then add to the left node if it is lexographically
        * less that the node above it else add it to the right node */
        else{
            if(strcmp(name, cnode->name) < 0)
                cnode->left  = add_to_c_tree(cnode->left,name);
            else if(strcmp(name, cnode->name) > 0)
                cnode->right = add_to_c_tree(cnode->right,name);

        }

        return cnode;
    }
    /* print_c_tree() : Print out binary tree */
    void print_c_tree(ctree *cnode){
        if (cnode != NULL) { 
            print_c_tree(cnode->left); 
            printf("%s\n",cnode->name); 
            print_c_tree(cnode->right);
        }
    }
    /* return_c_tree() : return array of strings containing all values in binary tree */
    void  return_c_tree   (ctree *cnode, char **array){

        if (cnode != NULL) { 
            return_c_tree (cnode->left,array+c_inc);
            c_tree_free(cnode->left); 
            *(array+c_inc++) = strdup(cnode->name);
            // printf("arr+%d:%s\n", c_inc-1,*(array+(c_inc-1)));
            return_c_tree (cnode->right,array+c_inc); 
            c_tree_free(cnode->right);
        }
    }
    /* c_tree_alloc() : Allocates space for a tree node */
    ctree *c_tree_alloc(void){
        return (ctree *) malloc(sizeof(ctree));
    }
    /* c_tree_free() : Free's Memory */
    void c_tree_free  (ctree *cnode){
        free(cnode);
    }

私はbt.cでテストしています:

    #include "btree.h"

    int main(void){

        ctree *node = NULL; char *arr[100];

        node = add_to_c_tree(node, "foo");
        node = add_to_c_tree(node, "yoo");
        node = add_to_c_tree(node, "doo");
        node = add_to_c_tree(node, "woo");
        node = add_to_c_tree(node, "aoo");
        node = add_to_c_tree(node, "boo");
        node = add_to_c_tree(node, "coo");

        print_c_tree(node);

        return_c_tree(node,arr);
        for (int i = 0; i < 7; ++i)
        {
            printf("%d:%s  ..\n",i, arr[i]);
        }
        return 0;
    }

この質問の理由は、return_c_tree() 関数に問題があったためです。この関数は、NULL ptr まで再帰的に自分自身を呼び出してノードの名前を出力する代わりに、K&R の print_c_tree() 関数の動作を模倣することを目的としています。辞書式の順序で、それらの名前を文字ptrの配列に追加し、ノードのメモリを解放することを意味します。

ただし、上記のように実行すると得られる出力は次のとおりです。

    aoo
    boo
    coo
    doo
    foo
    woo
    yoo
    0:aoo  ..
    1:(null)  ..
    2:boo  ..
    3:doo  ..
    4:foo  ..
    5:coo  ..
    6:(null)  ..

これは、print 関数は正常に動作しますが、return 関数は明らかにそうではないことを示しています。紛らわしいのは、return_c_tree() 内の printf() の呼び出しがコメント化されていない場合、次の結果になることです。

    aoo
    boo
    coo
    doo
    foo
    woo
    yoo
    arr+0:aoo
    arr+1:boo
    arr+2:coo
    arr+3:doo
    arr+4:foo
    arr+5:woo
    arr+6:yoo
    0:aoo  ..
    1:(null)  ..
    2:boo  ..
    3:doo  ..
    4:foo  ..
    5:coo  ..
    6:(null)  ..

これは、実際に文字列を正しい順序で追加することを意味します。また、c_inc変数なしで試しました->つまり、正しいノードに渡す前に配列をインクリメントするだけで、return_c_tree()のprintfから同じ結果が生成されますが、メインとは異なります:

    arr+-1:aoo
    arr+-1:boo
    arr+-1:coo
    arr+-1:doo
    arr+-1:foo
    arr+-1:woo
    arr+-1:yoo
    0:foo  ..
    1:yoo  ..
    2:coo  ..
    3:(null)  ..
    4:(null)  ..
    5:(null)  ..
    6:(null)  ..

私はかなり混乱しているので、誰かが助けてくれれば幸いです。間違った場所でインクリメントしているだけだと確信していますが、どこで解決できません。

ようやくポインターを理解したと思ったのですが、どうやらそうではありませんでした。

ベストP

4

1 に答える 1

5

あなたの問題は、array再帰的に呼び出すときにポインタをどのように処理するかです。return_c_treeこれにより、関数が修正されます。

void  return_c_tree   (ctree *cnode, char **array)
{

  if (cnode != NULL) { 
    return_c_tree (cnode->left,array); // <--- CHANGED 2ND PARAM
    c_tree_free(cnode->left); 
    *(array+c_inc++) = strdup(cnode->name);
    return_c_tree (cnode->right,array); // <--- AGAIN, CHANGED 2ND PARAM
    c_tree_free(cnode->right);
  }
}

グローバル変数c_incを使用して、配列への現在のインデックスを追跡しています。ただし、 を再帰的に呼び出したときreturn_c_treeに を渡しましたarray+c_incが、これを考慮して c_inc をオフセットしませんでした。c_inc基本的に、あなたは毎回二重に数えました。

これで特定の問題は解決しますが、コードには他にもいくつかの問題があります。

  1. 一般に、グローバル変数を使用すると問題が発生します。ここで行う必要はありません。c_incパラメータとして に渡しますreturn_c_tree

  2. また、グローバル変数と再帰を混在させると、特に問題が発生しやすくなります。再帰ルーチンがその状態をスタックに保持することが本当に必要です。

  3. コメンターが指摘したように、 のすべてのコードbtree.hは実際にはbtree.c. ヘッダー ファイルのポイントは、コードではなくインターフェイスを定義することです。

  4. これはより文体的です)あなたのreturn_c_tree関数は実際には2つの異なる関数です。ツリーの要素を(順番に)配列にコピーし、ツリーで使用されているメモリを解放します。これら 2 つの操作は概念的に異なります。両方ではなく、どちらか一方を実行したい場合があります。説得力のあるパフォーマンス (またはその他) の理由で 2 つを混在させることもできますが、確固たる証拠が得られるまで待ちます。

于 2012-07-17T22:35:44.813 に答える