二叉树到 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>

编辑

我正在尝试创建在 this codeplayer tutorial

中看到的标记

编辑 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 扩展。它在下面并且可以按我需要的方式工作。

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)
)

这会生成这样的 object:

{
  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} : {})

删除那些 undefineds,给你留下类似

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

节点类型

对于这个问题,我们要考虑两种不同类型的节点,operatorsnumbers。我们创建了两个简单的函数来包装它们:

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

请注意,这些类型和上面的 node/tree 函数比您从 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 等选择器访问节点,并为它们的 leftrightval 属性赋值。在任何动态情况下,我们可能都必须这样做。但是如果我们把整个结构放在前面,那就更干净了。

HTML代

我们的 preorder 函数访问每个节点两次,一次在 children(如果有)之前,一次在它们之后。所以我猜它不是真正的 preorder,而是 postorder 的组合。但这适用于我们想要做 before-and-after HTML 片段的 use-case。

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 的前后函数中生成标记,加入结果数组,并将其包装在请求的 <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))

总结

这并没有回答您关于为什么您的代码无法运行的基本问题,但它提供了我认为在 Javascript 中处理树木的更简单的解决方案,并创建了一种构建输出的方法你正在寻找一个。

Javascript 至少和 Object-Oriented 一样是一种函数式语言。这里的技术相当实用,使用简单、纯函数。我相信他们会制作出更简洁的程序。