簡體   English   中英

二叉樹到HTML列表

[英]Binary Tree to HTML list

我有一棵二叉樹,我想將其轉換為嵌套的HTML無序列表。

我有一個函數正在嘗試修改以完成任務。

我正在嘗試以下方法(運行輸出片段):

此方法在BinaryTreeClass中

inOrderTraverseHtml(start = this.rootPtr) {
    if (!start.isLeaf()) 
    {
        this.html+="<ul>"
    }
    else
    {
        this.html+="<li>"
    } // end if
    if (start.getLeftChild() !== null) 
    {
        this.inOrderTraverseHtml(start.getLeftChild());
    }; // end if
    this.html+=`<a href="#">${start.getItem().value}</a>`;
    if (start.getRightChild() !== null) 
    {
        this.inOrderTraverseHtml(start.getRightChild());
    }; // end if
    if (!start.isLeaf()) 
    {
        this.html+="</ul>"
    }
    else
    {
        this.html+="</li>"
    } // end if
} // end inOrderTraverseHtml  

這不會創建適當的列表項。 我收到了太多ul之上。

該代碼段包含我的完整代碼(包含ES6)

 /** * This is the Node class * Each item has a setter and getter */ class Node { constructor(item = null, id = null, leftChild = null, rightChild = null) { this.id = id; this.item = item; this.leftChildPtr = leftChild; this.rightChildPtr = rightChild; } setItem(item) { this.item = item; } getItem() { return this.item; } setId(id) { this.id = id; } getId() { return this.id; } isLeaf() { return this.leftChildPtr === null && this.rightChildPtr === null; } getLeftChild() { return this.leftChildPtr; } getRightChild() { return this.rightChildPtr; } setRightChild(rightPtr) { this.rightChildPtr = rightPtr; } setLeftChild(leftPtr) { this.leftChildPtr = leftPtr; } } /** * This is the MathModel class * Each item has a setter and getter * This gets inserted into the nodes */ class MathModel { constructor(type = "Operator", value = "+") { this.type = type; this.value = value; } Type() { return this.type; } setType(new_type) { this.type = new_type; } Value() { return this.value; } setValue(new_value) { this.value = new_value; } } /** * This is the BinaryNodeTree class * This is an ADT for a unbalenced binary tree * The ids for nodes will be phased out or at least be given a better index * for now I used it for an html canvas */ class BinaryNodeTree { constructor() { this.rootPtr = null; this.idRange = [ "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" ] this.ids = []; this.output = ""; this.html = ""; } setRoot(type, value) { let id = this.createId(); this.ids.push(id); let newNode = new Node(new MathModel(type, value), id); this.rootPtr = newNode; } getRoot() { return this.rootPtr; } createId(len = 6) { let string = ""; const rangeLength = this.idRange.length; for (let i = len; i--;) { string += this.idRange[Math.floor(Math.random() * rangeLength)] } return string; } // createId getHeightHelper(subTreePtr = new Node()) { if (subTreePtr === null || subTreePtr === new Node()) { return 0; } else { let a = this.getHeightHelper(subTreePtr.getLeftChild()); let b = this.getHeightHelper(subTreePtr.getRightChild()); let max = 0; if (a > b) { max = a; } else { max = b; } return 1 + max; } } // end getHeightHelper getNumberOfNodesHelper(subTreePtr = new Node()) { if (subTreePtr === null || subTreePtr === new Node()) { return 0; } else if (subTreePtr.isLeaf()) { return 0; } else if (subTreePtr.getLeftChild() === null && subTreePtr.getRightChild() !== null || subTreePtr.getLeftChild() !== null && subTreePtr.getRightChild() === null) { return 1; } else { return 2; } } // end getNumberOfNodesHelper /** * This will be an inorder traverse of the tree to find a node * @param {function} cb * @param {Node} treePtr * @param {*} target */ findNodeInOrder(cb, treePtr = this.rootPtr, targetId) { if (treePtr === null) { return null; } else if (treePtr.id === targetId) { return cb(treePtr); } else { this.findNodeInOrder(cb, treePtr.getLeftChild(), targetId); this.findNodeInOrder(cb, treePtr.getRightChild(), targetId); } } // end findNodeInOrder inOrderTraverse(cb, treePtr = this.rootPtr, parent = null) { if (treePtr !== null) { this.inOrderTraverse(cb, treePtr.getLeftChild(), treePtr); let Item = treePtr.getItem(); cb(Item, treePtr.id, treePtr, parent); this.inOrderTraverse(cb, treePtr.getRightChild(), treePtr); } } // end inOrderTraverse toString() { this.output = ""; this.inOrderTraversePrint(this.rootPtr) return this.output; } toHTML() { this.html = `<div class="tree">`; this.inOrderTraverseHtml(this.rootPtr) this.html += "</div>"; return this.html; } inOrderTraversePrint(start = this.rootPtr) { if (!start.isLeaf()) { // console.log("("); this.output += "(" } if (start.getLeftChild() !== null) { this.inOrderTraversePrint(start.getLeftChild()); }; // end if // console.log(start.getItem().value); this.output += start.getItem().value; if (start.getRightChild() !== null) { this.inOrderTraversePrint(start.getRightChild()); }; // end if if (!start.isLeaf()) { // console.log(")"); this.output += ")"; } } // end inOrderTraversePrint inOrderTraverseHtml(start = this.rootPtr) { if (!start.isLeaf()) { this.html += "<ul>" } else { this.html += "<li>" } // end if if (start.getLeftChild() !== null) { this.inOrderTraverseHtml(start.getLeftChild()); }; // end if this.html += `<a href="#">${start.getItem().value}</a>`; if (start.getRightChild() !== null) { this.inOrderTraverseHtml(start.getRightChild()); }; // end if if (!start.isLeaf()) { this.html += "</ul>" } else { this.html += "</li>" } // end if } // end inOrderTraverseHtml preOrderTraverse(cb, treePtr = this.rootPtr) { if (treePtr !== null) { let Item = treePtr.getItem(); cb(Item, treePtr.id); this.inOrderTraverse(cb, treePtr.getLeftChild()); this.inOrderTraverse(cb, treePtr.getRightChild()); } } // end preOrderTraverse postOrderTraverse(cb, treePtr = this.rootPtr) { if (treePtr !== null) { this.inOrderTraverse(cb, treePtr.getLeftChild()); this.inOrderTraverse(cb, treePtr.getRightChild()); let Item = treePtr.getItem(); cb(Item, treePtr.id); } } // end postOrderTraverse addLeft(treePtr = new Node(), newItem) { let id = this.createId(); while (this.ids.indexOf(id) !== -1) { id = this.createId(); } let newNode = new Node(newItem, id); if (treePtr.getLeftChild() !== null) { let tempPtr = treePtr.getLeftChild(); newNode.setLeftChild(tempPtr); treePtr.setLeftChild(newNode); } else { treePtr.setLeftChild(newNode); } } // end addLeft addRight(treePtr = new Node(), newItem) { let id = this.createId(); while (this.ids.indexOf(id) !== -1) { id = this.createId(); } let newNode = new Node(newItem, id); if (treePtr.getRightChild() !== null) { let tempPtr = treePtr.getRightChild(); newNode.setRightChild(tempPtr); treePtr.setRightChild(newNode); } else { treePtr.setRightChild(newNode); } } // end addRight removeFromIdsHelper(id) { let index = this.ids.indexOf(id); this.ids.splice(index, 1); } // end removeFromIdsHelper removeRight(treePtr = new Node(), newItem) { this.removeFromIdsHelper(treePtr.getRightChild().id); treePtr.setRightChild(null) // todo: handle existing nodes in children } // end removeRight removeLeft(treePtr = new Node(), newItem) { this.removeFromIdsHelper(treePtr.getLeftChild().id); treePtr.setLeftChild(null) // todo: handle existing nodes in children } // end removeLeft } /** * This is the implementation of the Abstract data type */ let tree = new BinaryNodeTree(); function handleCreateClick() { $("[data-action=start]").off().on("click", function() { tree.setRoot("Operator", "+"); tree.addLeft(tree.getRoot(), new MathModel("Operator", "-")) tree.addRight(tree.getRoot(), new MathModel("Number", 20)) tree.addLeft(tree.getRoot().getLeftChild(), new MathModel("Operator", "/")) tree.addRight(tree.getRoot().getLeftChild(), new MathModel("Number", 60)) tree.addLeft(tree.getRoot().getLeftChild().getLeftChild(), new MathModel("Number", 75)) tree.addRight(tree.getRoot().getLeftChild().getLeftChild(), new MathModel("Number", 60)) console.log("Tree created", "Uncomment line 299 to log it."); // console.log(tree); }); } function handleDrawClick() { $("[data-action=draw]").off().on("click", function() { console.log(tree.toString()); $(".output").html(tree.toHTML()) }); } function invokes() { handleCreateClick(); handleDrawClick(); } $(document).ready(() => { invokes(); }) 
 body { height: 1000px; } 
 <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4" crossorigin="anonymous"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <div class="container"> <div class="row"> <div class="col-xs-12"> <h1>TREE</h1> <ul> <li>The Node class starts on line 5</li> <li>The Math Model class starts on line 45</li> <li>The Binary Tree class starts on line 69 <ul> <li>toHTML() starts on line 168</li> <li>toHTML() calls inOrderTraverseHtml() which is on line 182</li> <li>This gets called from the implementation on the <span class="btn btn-sm btn-info">Draw</span> event on line 302</li> </ul> </li> <li>The implementation of the Abstract data type starts in JS on line 269</li> <li>Uncomment line 307 to view the tree</li> </ul> <h2>To Start</h2> <ol> <li>Click start</li> <li>Click draw</li> </ol> </div> </div> <hr/> <div class="row"> <div class="col-xs-12"> <div class="btn-group"> <div class="btn btn-primary" data-action="start">Start!</div> <div class="btn btn-info" data-action="draw">Draw!</div> </div> </div> <div class="col-xs-12"> <div class="output"></div> </div> </div> </div> 

編輯

我正在嘗試創建此代碼播放器教程中看到的標記

編輯2

我正在提供一個示例,說明我希望輸出如何顯示。

<div class="tree">
<ul>
    <li>
        <a href="#">Parent</a>
        <ul>
            <li>
                <a href="#">Child</a>
                <ul>
                    <li>
                        <a href="#">Grand Child</a>
                    </li>
                </ul>
            </li>
            <li>
                <a href="#">Child</a>
                <ul>
                    <li><a href="#">Grand Child</a></li>
                    <li>
                        <a href="#">Grand Child</a>
                        <ul>
                            <li>
                                <a href="#">Great Grand Child</a>
                            </li>
                            <li>
                                <a href="#">Great Grand Child</a>
                            </li>
                            <li>
                                <a href="#">Great Grand Child</a>
                            </li>
                        </ul>
                    </li>
                    <li><a href="#">Grand Child</a></li>
                </ul>
            </li>
        </ul>
    </li>
</ul>

因此,我向BinaryNodeTree添加了一個類擴展。 它在下面,並且按我需要的方式工作。

class BinaryNodeTreeToHTML extends BinaryNodeTree {
    constructor(rootPtr, className = "tree-html") {
        super(rootPtr);
        this.html = [];
        this.rootPtr = rootPtr;
        this.className = className;
    }
    createContainer() {
        const _ = this;
        return new $("<div />", {
            "class": _.className
        });
    }
    createListItem(type, value, id, parentId) {
        const _ = this;
        return $("<li />", {
            id,
            "data-parent_id": parentId,
            "data-type": type,
            "data-value": value,
            html: _.createAnchor(type, value)
        });
    }
    createAnchor(type, value) {
        return $("<a />", {
            href: "#",
            "data-type": type,
            text: value
        });
    }
    createUnorderedList(parent = "root") {
        return $("<ul />", {
            "data-parent": parent
        })
    }
    Start(outputClassName) {
        const _ = this;
        console.log(this.Root);
        const $output = $(`.${outputClassName}`).eq(0);
        const $container = this.createContainer();
        let $main_ul = _.createUnorderedList();
        $container.append($main_ul);
        $output.append($container);
        console.log(_.html);
        this.inOrderTraverse((Item, Id, Pointer, Parent) => {
            if (Parent !== null) {
                let $new_item = _.createListItem(Item.Type, Item.Value, Id, Parent.Id);
                $main_ul.append($new_item);
            } else {
                let $new_item = _.createListItem(Item.Type, Item.Value, Id, "root");
                $main_ul.append($new_item);
            }
        })
        for(let obj of $main_ul.find("li")){
            let $obj = $(obj);
            if($obj.data().parent_id !== "root"){
                let $parent = $("#"+$obj.data().parent_id);
                if($parent.find("[data-parent="+$parent.attr("id")+"]").length > 0){
                    let $ul = $parent.find("[data-parent="+$parent.attr("id")+"]")
                    $ul.append($obj);
                }else{
                    $parent.append(_.createUnorderedList($parent.attr("id")).append($obj))
                }
            }

        }

        return $container;
    }
};

我一直在思考這個問題。 我很高興您想出了一些適合您的東西。 我仍然沒有嘗試通過您的代碼來工作。 我那里有限的注意力實在太多了。

但是潛在的問題使我感興趣,並且我有一個完全不同的解決方案。

更簡單的樹

我們從簡單得多的樹結構開始。

const node = (val, left, right) => ({val, left, right})

const preorder = (before, after, node) =>
  [before(node.val)]
  .concat(node.left ? preorder(before, after, node.left) : [])
  .concat(node.right ? preorder(before, after, node.right) : [])
  .concat(after(node.val))

該問題似乎需要preorder遍歷,而不是inorder遍歷。 如果我們要進行inorder遍歷,則非常簡單:

const inorder = (fn, node) => 
  (node.left ? inorder(fn, node.left) : [])
  .concat(fn(node.val))
  .concat(node.right ? inorder(fn, node.right) : [])

請注意,這些遍歷只是將每個節點上的函數調用結果收集到一個數組中。 為了處理您希望HTML節點在預購版本中相互包裹這一事實,我們有單獨的beforeafter函數。 我們將使用它。

這樣構造的樹可能非常簡單:

node(
  '+',
  18,
  node('*', 4, 6)
)

生成這樣的對象:

{
  val: "+",
  left: 18,
  right: {
    val: "*",
    left: 4,
    right: 6
  }
}

或者,如果您願意,您可以

node(
  node('+'),
  node(18),
  node('*', node(4), node(6))
)

看起來更像這樣:

{
  val: {
    val: "+",
    left: undefined,
    right: undefined
  },
  left: {
    val: 18,
    left: undefined,
    right: undefined
  },
  right: {
    val: "*",
    left: {
        val: 4,
        left: undefined,
        right: undefined
    },
    right: {
        val: 6,
        left: undefined,
        right: undefined
    }
  }
}

如果要執行此操作,則可能更喜歡這個稍微復雜一點的node版本:

const node = (val, left, right) => 
    Object.assign({val}, left ? {left} : {}, right ? {right} : {})

這將刪除那些undefined的,從而為您提供類似的輸出

{
  val: {
    val: "+"
  },
  left: {
    val: 18
  },
  right: {
    val: "*",
    left: {
      val: 4
    },
    right: {
      val: 6
    }
  }
}

節點類型

對於此問題,我們要考慮兩種不同類型的節點: 運算符數字 我們創建了兩個簡單的函數來包裝它們:

const operator = (value) => ({type: 'op', value})
const number = (value) => ({type: 'num', value})

請注意,這些類型和上面的節點/樹函數比您從OO語言獲得的端口要簡單得多。 Javascript是一種較高級別的語言,在很多方面它可以比Java / C ++ / C#/ etc更簡單。

樣本數據

使用這些節點類型,我們可以創建請求的樹,如下所示:

const myTree = node(
  operator('+'),
  node(
    operator('-'), 
    node(
      operator('/'),
      node(number(60)),
      node(number(75)),
    ),
    node(number(60))
  ),
  node(number(20))
)

問題在於,這種構建是更加零碎的。 我們可以按照自己的myTree.left.left.right做這些事情,通過選擇器(例如myTree.left.left.right訪問節點,並將值分配給它們的leftrightval屬性。 在任何動態情況下,我們可能都必須這樣做。 但是,如果我們預先准備好整個結構,這將更加清潔。

HTML生成

我們的preorder功能對每個節點訪問兩次,一次在子節點(如果有)之前,一次在子節點之后。 所以我想這不是真正的preorder ,而是preorderpostorder的結合。 但這適用於我們的用例,即想要在HTML片段之前和之后進行操作。

const toHtml = (tree) => `<div class='tree'><ul>` + preorder(
  node => node.type === 'op' 
      ? `<li><a class="${node.type}" href="#">${node.value}</a><ul>` 
      : `<li><a class="${node.type}" href="#">${node.value}</a></li>`,
  node => node.type === 'op' 
      ? `</ul></li>` 
      : ``, 
  tree
).join('') + `</ul></div>`

這是非常特定於我們的問題的,它在提供給preorder的before和after函數中生成標記,加入結果數組,並將其包裝在請求的<div>標簽中。 我們將CSS類添加到標記中,以防萬一。

用法

放在一起,我們得到

toHtml(myTree)

會產生類似

<div class='tree'>
  <ul>
    <li>
      <a class="op" href="#">+</a>
      <ul>
        <li>
          <a class="op" href="#">-</a>
          <ul>
            <li>
              <a class="op" href="#">/</a>
              <ul>
                <li>
                  <a class="num" href="#">60</a>
                </li>
                <li>
                  <a class="num" href="#">75</a>
                </li>
              </ul>
            </li>
            <li> 
              <a class="num" href="#">60</a>
            </li>
          </ul>
        </li>
        <li>
          <a class="num" href="#">20</a>
        </li>
      </ul>
    </li>
  </ul>
</div>

(盡管沒有花哨的縮進。)

演示版

我們可以在以下代碼段中看到它們如何一起工作:

 // Binary Tree Implementation const node = (val, left, right) => ({val, left, right}) const preorder = (before, after, node) => [before(node.val)] .concat(node.left ? preorder(before, after, node.left) : []) .concat(node.right ? preorder(before, after, node.right) : []) .concat(after(node.val)) // Node types const operator = (value) => ({type: 'op', value}) const number = (value) => ({type: 'num', value}) // HTML Generation const toHtml = (tree) => `<div class='tree'><ul>` + preorder( node => node.type === 'op' ? `<li><a class="${node.type}" href="#">${node.value}</a><ul>` : `<li><a class="${node.type}" href="#">${node.value}</a></li>`, node => node.type === 'op' ? `</ul></li>` : ``, tree ).join('') + `</ul></div>` // Sample Data const myTree = node( operator('+'), node( operator('-'), node( operator('/'), node(number(60)), node(number(75)), ), node(number(60)) ), node(number(20)) ) // Usage console.log(toHtml(myTree)) 

摘要

這不能回答有關代碼為何無法正常工作的基本問題,但它提供了我認為是處理Java中的樹的更簡單解決方案,並提供了一種構建所需的輸出的方法。

Java語言至少是一種功能語言,而它是一種面向對象的語言。 這里的技術具有合理的功能,可以使用簡單的純函數。 我相信他們會制定出更清潔的程序。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM