PTA習題解析——目錄樹


目錄樹


看到這個問題,我們是一頭霧水啊,這講了個啥?別急,讓我們用測試樣例模擬一遍。

樣例模擬

首先我們先考慮存儲數據的方式,根據觀察和我們對文件夾的理解,對於一個文件夾而言,與其他文件或文件夾只會有 2 種關系——和我在同一目錄、在我的目錄,也就是只有同級和下級兩種關系。因此我們就很自然地想到孩子兄弟表示法,因為在孩子兄弟對於一個結點也只會有 2 種關系——孩子和兄弟,那我們就用孩子表示在下級目錄,兄弟表示在同級目錄。

首先我們擁有一個根目錄 root,讀取第一行數據,表示根目錄有個名為 b 的文件,是 root 的孩子,因此根據孩子兄弟表示法,b 應該是 root 的左分支結點。

讀取第二行數據,在根目錄中有個 c 目錄。因此 c 目錄是 root 的孩子,並且與 b 文件同級,也就是互為兄弟關系,進行結點的加入。

讀取第三行數據,在根目錄中有個 ab 目錄,並在這個目錄下有個 cd 文件。因此 ab 目錄是 root 的孩子,並且與 c 目錄同級,也就是互為兄弟關系,同時 ab 目錄有個左分支 cd,進行結點的加入。

讀取第四行數據,在根目錄中有個 a 目錄,並在這個目錄下有個 bc 文件。因此 a 目錄是 root 的孩子,並且與 ab 目錄同級,也就是互為兄弟關系,同時 a 目錄有個左分支 bc,進行結點的加入。

讀取第五行數據,在 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 類型來當 flag 而不是 bool 類型呢?這是因為改為 int 類型,相當於直接賦予優先級,判斷插入位置時直接在同優先級的結點進行查找即可。

建樹算法

字符串切片算法

切片算法可以用字符數組實現,也可以用 string 類實現,此處我用字符數組描述。由於我們讀入的數據是字符串,因此我們需要先把各個目錄分離開來。需要注意的是,雖然文件是特殊數據,但是文件只會出現在字符串串尾,因此只需要一個分支結構單獨處理即可。

偽代碼


需要強調的是,string 類用來判斷字典序和復制操作都可以直接用運算符實現,更為方便。

代碼實現

調試結果

偽代碼

建樹算法只需要再字符串切片算法進行改動即可,把輸出語句改為調用結點插入函數即可實現。

代碼實現

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;
    }

    //要先判空,不然有段錯誤
    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)    //目錄或文件已存在(第三版就是因為這個出問題)
    {
        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);    //下一層多兩個空格
    //cout << T->data << " " ;    //中序遍歷
    PreOrderTraverse(T->rightsib,space);    //兄弟結點不需要多空格
    //cout << T->data << " " ;    //后序遍歷
}

主函數

測試樣例

輸入樣例

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM