[英]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節點在預購版本中相互包裹這一事實,我們有單獨的before
和after
函數。 我們將使用它。
這樣構造的樹可能非常簡單:
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
訪問節點,並將值分配給它們的left
, right
和val
屬性。 在任何動態情況下,我們可能都必須這樣做。 但是,如果我們預先准備好整個結構,這將更加清潔。
我們的preorder
功能對每個節點訪問兩次,一次在子節點(如果有)之前,一次在子節點之后。 所以我想這不是真正的preorder
,而是preorder
和postorder
的結合。 但這適用於我們的用例,即想要在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.