PTA問題分析-ディレクトリツリー

ディレクトリツリー


この問題を見て、私たちは混乱しています、あなたは何と言いましたか?心配しないで、テストサンプルでシミュレートしましょう。

サンプルシミュレーション

最初に、データの保存方法を最初に検討します。観察とフォルダーの理解によると、フォルダーの場合、他のファイルまたはフォルダーとの関係は2つのみです。 、つまり、同じレベルと下位レベルの関係は2つしかありません。したがって、当然のことながら、子兄弟表記はノード子と兄弟の2種類の関係しか持てないため、子兄弟表記を考えます。次に、子を使用して下位ディレクトリで表し、兄弟は同じディレクトリで表します。

まず、ルートディレクトリルートがあり、データの最初の行を読み取ります。これは、ルートディレクトリに、ルートの子であるbという名前のファイルがあることを示します。したがって、子兄弟表記によれば、bはルートの左ブランチである必要があります。

データの2行目を読みます。ルートディレクトリにcディレクトリがあります。したがって、cディレクトリーはrootの子であり、bファイルと同じレベルにあります。つまり、互いに兄弟関係にあり、ノードが追加されます。

データの3行目を読みます。ルートディレクトリにabディレクトリがあり、このディレクトリにcdファイルがあります。したがって、abディレクトリはルートの子であり、cディレクトリと同じレベルにあります。つまり、互いに兄弟関係にあり、abディレクトリにはノードに参加するための左ブランチcdがあります。

データの4行目を読みます。ルートディレクトリにディレクトリがあり、このディレクトリにbcファイルがあります。したがって、aディレクトリーはrootの子であり、abディレクトリーと同じレベル、つまり互いに兄弟関係にあると同時に、aディレクトリーにはノードに参加するための左ブランチbcがあります。

データの5行目を読みます。abディレクトリにdファイルがあります。abはすでに存在しているため、dとcdは互いに兄弟関係にあるため、abの子をdに、dの兄弟をcdに変更して、ノードに参加します。

上記の操作を繰り返して、ツリーの構築を完了します。


このツリーをバイナリツリーの形式に編成します。

このレッスンのツリーを前のトラバーサルの順序で読んだところ、インデントを無視して、つまり、ツリーが構築されている限り、このシナリオでは、読み取りの順序がサンプルの出力データとまったく同じであることがわかりました。解決しました。アプリケーションでは、子兄弟表記を使用してツリーを構築することを積極的に検討する必要があります。このメソッドはバイナリツリーを構築するため、バイナリツリーの基本操作を使用してこのツリーを操作できます。

ノード構造定義

typedef struct CSNode
{
    string data;    //数据域
    struct CSNode* firstchild;    //指向对应长子结点的指针域
    struct CSNode* rightsib;    //指向对应右兄弟结点的指针域
    int flag_file;    //判断是文件还是目录的 flag
}CSNode, * CSTree;
  • ブール型ではなく、整数型をここでフラグとして使用するのはなぜですか?これは、int型に変更することは直接優先順位を付けることと同じであり、挿入位置を決定するときに同じ優先順位を持つノードを直接検索できるためです。

ツリー構築アルゴリズム

文字列スライスアルゴリズム

スライスアルゴリズムは文字配列を使用して実装することも、文字列クラスを使用して実装することもできますが、ここでは文字配列を使用して説明しています。読み取るデータは文字列であるため、最初にさまざまなディレクトリを分離する必要があります。ファイルは特別なデータですが、ファイルは文字列の最後にのみ表示されるため、ブランチ構造のみを個別に処理する必要があることに注意してください。

疑似コード


辞書編集順序とコピー操作を決定するために使用される文字列クラスは、演算子を使用して直接実装できるため、より便利です。

コードの実装

試運転結果

疑似コード

ツリー構築アルゴリズムは、文字列スライスアルゴリズムによってのみ変更する必要があり、出力ステートメントは、ノード挿入関数を呼び出すことによって実装できます。

コードの実装

void createTree(CSTree pre, string str)
{
    int idx = 0;

    getline(cin, str);
    for (int i = 0; i < str.size(); i++)
    {
        if (str[i] == '\\')    //注意用反义字符,不然会报错
        {                              //只要不在串尾,只会是目录
            pre = insertNode(pre, str.substr(idx, i - idx), 1);
            idx = i + 1;    //移动字符串到下一个目录,即 '\' 之后
        }
    }
    if (idx < str.size())    //文件只出现在字符串尾
    {
        pre = insertNode(pre, str.substr(idx, str.size() - idx), 0);
    }
}

ノード挿入アルゴリズム

このアルゴリズムの目的は、新しいノードをツリー構造の正しい位置に挿入することです。これは、この問題を解決するための中核です。ここでは、戻り値の重要性を強調する必要があります。戻り値を設定せずにディレクトリを参照する場合は、呼び出し元の関数に戻るときにポインタを現在のディレクトリに移動する必要があります。これは、より面倒です。連絡先はディレクトリとして使用され、戻り値は関数呼び出しの場所を返すために使用されます。エラーが発生しやすい場所は、挿入位置がディレクトリの最長の子ノードである場合、プリカーサポインタの後続を介した直接の操作が誤った位置に挿入され、リンクが切断されることです。したがって、この問題を回避するには、現在位置ポインタとプリカーサポインタを設定する必要があります。

疑似コード

コードの実装

CSTree insertNode(CSTree t, string str, int flag)    //解决问题的核心
{
    CSTree a_node = new CSNode;
    CSTree pre = t, ptr;

    a_node->data = str;    //初始化新结点
    a_node->firstchild = a_node->rightsib = NULL;
    a_node->flag_file = flag;

    if (t->firstchild == NULL)    //所在目录没孩子,直接插入结点
    {
        t->firstchild = a_node;
        return t->firstchild;
    }

    ptr = t->firstchild;    //由于根结点本身插入时,是插在长子位,因此另外设置 pre 当前驱结点,ptr 当 pre 的后继
    while (ptr != NULL && ((ptr->flag_file > a_node->flag_file) || (ptr->flag_file == a_node->flag_file && str > ptr->data)))
    {
        pre = ptr;
        ptr = ptr->rightsib;    //同时移动 ptr 和其前驱,这么做的好处是不需要考虑长子结点和兄弟结点的关系
    }

    //要先判空,不然有段错误
    if (ptr == NULL)    //无处可插入,插在链尾
    {
        a_node->rightsib = pre->rightsib;
        pre->rightsib = a_node;
        return a_node;    //接下来以 a_node 为根目录操作
    }
    else if (ptr->data == a_node->data && ptr->flag_file == a_node->flag_file)    //目录或文件已存在(之前因为这个出了 bug)
    {
        delete a_node;    //把申请的新结点打掉
        return ptr;    //接下来在已有的 ptr 目录下操作
    }
    else    //找到了应该插入的位置
    {
        if (pre->data == t->data)    //插在根目录的长子位
        {
            a_node->rightsib = pre->firstchild;
            pre->firstchild = a_node;
        }
        else    //正常插入
        {
            a_node->rightsib = pre->rightsib;
            pre->rightsib = a_node;
        }
        return a_node;    //接下来以 a_node 为根目录操作
    }
}

ディレクトリツリーを印刷する

出力ノードの順序が最初にバイナリツリーをトラバースする順序であることはすでにわかっているため、インデントメカニズムを追加するだけでそれを実現できます。

void PreOrderTraverse(CSTree T, int space)
{                                 //因为要输出空格,稍微改装遍历算法
    if (T == NULL)
        return;
    for (int i = 0; i < space; i++)
    {
        cout << " ";
    }
    cout << T->data << endl;    //前序遍历
    PreOrderTraverse(T->firstchild, space + 2);    //下一层多两个空格
    PreOrderTraverse(T->rightsib, space);    //兄弟结点不需要多空格
}

主な機能

試験サンプル

入力サンプル

15
b
c\
ab\cd
a\bc
ab\d
a\d\a
a\d\z\
b\
c
ab\cd\e
a\bc\f
ab\d\g
a\d\a\h
a\d\z

出力例

root
  a
    bc
      f
    d
      a
        h
      z
      a
      z
    bc
  ab
    cd
      e
    d
      g
    cd
    d
  b
  c
  b
  c

おすすめ

転載: www.cnblogs.com/linfangnan/p/12617499.html