Невозможно определить ошибку logi c при обходе, чтобы проверить, есть ли в дереве конкретное поддерево внутри него - PullRequest
2 голосов
/ 09 мая 2020

Я пытаюсь решить следующий вопрос:

Учитывая два непустых двоичных дерева s и t, проверьте, имеет ли дерево t точно такую ​​же структуру и значения узлов с поддеревом s. Поддерево s - это дерево, состоящее из узла в s и всех потомков этого узла. Дерево s также можно рассматривать как поддерево самого себя.

Пример 1:

Given tree s:
     3
    / \
   4   5
  / \
 1   2

Given tree t:
   4 
  / \
 1   2

Возвращает истину, потому что t имеет ту же структуру и значения узлов с поддеревом s.

Пример 2:

Given tree s:
     3
    / \
   4   5
  / \
 1   2
    /
   0

Given tree t:
   4
  / \
 1   2

Вернуть false.

Я написал следующий код. Я считаю, что это правильно сравнивает деревья, но я не возвращаю правильное значение для последнего случая.

class TreeNode {
    constructor(val, left, right) {
        this.val = (val === undefined ? 0 : val)
        this.left = (left === undefined ? null : left)
        this.right = (right === undefined ? null : right)
    }
}

const isSubtree = (s, t) => {
    if (!s || !t) return false
    let sameTree = false

const isSubtree = (s, t) => {
    if (!s || !t) return false
    let sameTree = false

    //changed to preOrder, but it does not work for left or right skewed trees
    const dfsPO = c => {
        if (!c) return
        if (c.val === t.val) sameTree = isSameTree(c, t)
        if (c.left) dfsPO(c.left)
        if (c.right) dfsPO(c.right)
        return sameTree
    }
    return sameTree = dfsPO(s)
}

const isSameTree = (c, t) => {
    if (!c && !t) return true
    if (!c || !t) return false
    if (c.val !== t.val) return false
    return isSameTree(c.left, t.left) && isSameTree(c.right, t.right)
}

Вот тестовые примеры:

const tree1 = new TreeNode(3, new TreeNode(4, new TreeNode(1), new TreeNode(2)), new TreeNode(5))
const tree2 = new TreeNode(4, new TreeNode(1), new TreeNode(2))

const tree3 = new TreeNode(3, new TreeNode(4, new TreeNode(1), new TreeNode(2, new TreeNode(0))), new TreeNode(5))
const tree4 = new TreeNode(4, new TreeNode(1), new TreeNode(2))

const tree5 = new TreeNode(1, new TreeNode(1))
const tree6 = new TreeNode(1)

console.log(isSubtree(tree1, tree2)) //true
console.log(isSubtree(tree3, tree4)) //false
console.log(isSubtree(tree5, tree6)) //true 

//the input for the tree that fails is as follows:

//[1,null,1,null,1,null,1,null,1,null,1,null,1,null,1,null,1,null,1,null,1,2]
//[1,null,1,null,1,null,1,null,1,null,1,2]

Мне нужна помощь в выяснении где недостаток в моем логе c - деревья с наклоном влево или вправо.

Ответы [ 3 ]

4 голосов
/ 09 мая 2020

Какой забавный вопрос!

const empty =
  {}

const tree = (v = null, l = empty, r = empty) =>
  ({ v, l, r })

Нам нужны два дерева, t1 и t2 -

t1:
     3
    / \
   4   5
  / \
 1   2

t2:
   4
  / \
 1   2

Мы можем легко написать их, используя tree -

const t1 =
  tree
    ( 3
    , tree(4, tree(1), tree(2))
    , tree(5)
    )

const t2 =
  tree(4, tree(1), tree(2))

Я думаю, у вас есть правильная идея для тестирования, если два дерева equal -

  1. Если оба t1 и t2 пусты, ничего не осталось для сравнения верните true
  2. По индукции и t1, и t2 не пусты. Если t1 xor t2 пусто, вернуть false
  3. По индукции ни t1, ни t2 не пусты. Если t1.v соответствует t2.v, повторите и сравните каждое поддерево
const equal = (t1 = empty, t2 = empty) =>
  t1 === empty && t2 === empty
    ? true                       // 1
: t1 === empty || t2 === empty
    ? false                      // 2
: t1.v === t2.v                  // 3
    && equal(t1.l, t2.l)
    && equal(t2.l, t2.r)

Мы можем написать isSubTree -

  1. Когда t пусто, s является поддеревом t, если s пусто
  2. По индукции t не пусто. вернуть equal(t,s) или повторить t.l или повторить t.r
const isSubTree = (t = empty, s = empty) =>
  t === empty
    ? s === empty          // 1
    : equal(t, s)          // 2
      || isSubTree(t.l, s)
      || isSubTree(t.r, s)

Посмотрите код в действии! Проверьте результаты в своем браузере ниже -

const empty =
  {}

const tree = (v = null, l = empty, r = empty) =>
  ({ v, l, r })

const equal = (t1 = empty, t2 = empty) =>
  t1 === empty && t2 === empty
    ? true
: t1 === empty || t2 === empty
    ? false
: t1.v === t2.v
    && equal(t1.l, t2.l)
    && equal(t1.r, t2.r)

const isSubTree = (t = empty, s = empty) =>
  t === empty
    ? s === empty
    : equal(t, s)
      || isSubTree(t.l, s)
      || isSubTree(t.r, s)

const t1 =
  tree
    ( 3
    , tree(4, tree(1), tree(2))
    , tree(5)
    )

const t2 =
  tree(4, tree(1), tree(2))

const t3 =
  tree(4, tree(1), tree(9))

console.log(isSubTree(t1, t2)) // true
console.log(isSubTree(t1, t3)) // false

Я надеюсь, что этот подход показывает вам, что иногда меньше значит больше, когда дело доходит до написания программ.



boolean logi c

Этот вопрос дает хорошую возможность начать изучение логических logi c. Если вы похожи на меня, вам не нравится писать условные выражения вроде -

if (someCondition)
  return true
else
  return false

return someCondition ? true : false

Поскольку someCondition уже является логическим, в обоих случаях проще написать -

return someCondition

Когда мы написали equal, мы видим, что возвращаем true и false в некоторых ветвях кода. Но не совсем просто увидеть, как их можно очистить ...

const equal = (t1 = empty, t2 = empty) =>
  // can we collapse the explicit bools?
  t1 === empty && t2 === empty
    ? true                       // <-- explicit bool
: t1 === empty || t2 === empty
    ? false                      // <-- explicit bool
: t1.v === t2.v
    && equal(t1.l, t2.l)
    && equal(t1.r, t2.r)

Мы не хотим безрассудно гадать, какой лог c правильный. Мы подойдем к этому методично, используя таблицу истинности, чтобы получить надежный ответ -


│ p := (t1 === empty)
│ q := (t2 === empty)

┌───┬───┬──────────┬───────────┬─────────┬──────────┬──────────┬───────────┐
│ p │ q │ p 'and q │ p 'nand q │ p 'or q │ p 'nor q │ p 'xor q │ p 'xnor q │
├───┼───┼──────────┼───────────┼─────────┼──────────┼──────────┼───────────┤
│ 1 │ 1 │    1     │     0     │    1    │    0     │    0     │     1     │
│ 1 │ 0 │    0     │     1     │    1    │    0     │    1     │     0     │
│ 0 │ 1 │    0     │     1     │    1    │    0     │    1     │     0     │
│ 0 │ 0 │    0     │     1     │    0    │    1     │    0     │     1     │
└───┴───┴──────────┴───────────┴─────────┴──────────┴──────────┴───────────┘

Ссылаясь на нашу таблицу истинности, мы видим, что and и nor идеально описывают наши логические логические значения c -

const equal = (t1 = empty, t2 = empty) =>
                                 //┌───┬───┬┬──────────┬──────────┐
                                 //│ p │ q ││ p 'and q │ p 'nor q │
                                 //├───┼───┼┼──────────┼──────────┤
  t1 === empty && t2 === empty   
    ? true                       //│ 1 │ 1 ││    1     │    0     │

: t1 === empty || t2 === empty   
    ? false                      //│ 1 │ 0 ││    0     │    0     │
                                 //│ 0 │ 1 ││    0     │    0     │

                                 //│ 0 │ 0 ││    0     │    1     │
: t1.v === t2.v                  //└───┴───┴┴──────────┴──────────┘
    && equal(t1.l, t2.l)         
    && equal(t1.r, t2.r)   

Использование and соответствует двум верхним условиям и ветвям кода; nor соответствует последней ветви, в которой мы повторяемся -

const nor = (x, y) =>
  !(Boolean(x) || Boolean(y))

const equal = (t1 = empty, t2 = empty) =>
                                  //┌───┬───┬┬──────────┬──────────┐
                                  //│ p │ q ││ p 'and q │ p 'nor q │
                                  //├───┼───┼┼──────────┼──────────┤
  nor(t1 === empty, t2 === empty) //│ 0 │ 0 ││    0     │    1     │
    ? t1.v === t2.v
        && equal(t1.l, t2.l)
        && equal(t1.r, t2.r)
    : t1 === empty && t2 === empty//│ 1 │ 0 ││    0     │    0     │
                                  //│ 0 │ 1 ││    0     │    0     │
                                  //│ 1 │ 1 ││    1     │    0     │

Или простыми словами -

  1. Если ни одно дерево не пусто, деревья равны, если значение совпадает и поддеревья равны
  2. По индукции хотя бы одно дерево пусто. Деревья равны, только если оба дерева пусты.
const equal = (t1 = empty, t2 = empty) =>
  nor(t1 === empty, t2 === empty)
    ? t1.v === t2.v                 // 1
        && equal(t1.l, t2.l)
        && equal(t1.r, t2.r)
    : t1 === empty && t2 === empty  // 2

NB, мы звоним nor перед and (&&). Это потому, что мы хотим повторяться только тогда, когда ни одно дерево не пусто. Поскольку and и nor возвращают один и тот же ответ для (p = 1, q = 0) и (p = 0, q = 1), мы можем сделать рекурсию эксклюзивной только для ветки nor, поставив ее первой.

1 голос
/ 09 мая 2020

isSameTree выглядит нормально. За isSubTree сложно следить, но в основном все, что вам нужно сделать, это пройти s и запустить isSameTree для каждого поддерева, имеющего корень на каждом узле в s. Если в какой-то момент в isSubtree мы обнаруживаем, что тот или иной узел имеет значение NULL, нам нужно проверить, что они оба равны нулю, прежде чем предполагать успех, что является тем же logi c, необходимым в equalTrees.

Есть место для повышения эффективности - похоже, это O(st), где для каждого узла в s мы проверяем каждый узел в t. В потоке LeetCode есть интересные оптимизации, включая хеширование Меркла и проверку максимальной глубины каждого дерева, а также выполнение сравнений только на одном возможном уровне, где поддеревья могут совпадать. Также существуют решения по стрингификации.

const equalTrees = (s, t) => {
  if (!s || !t) return s === t;
  
  return s.val === t.val && 
         equalTrees(s.left, t.left) &&
         equalTrees(s.right, t.right);
};

const isSubtree = (s, t) => {
  if (!s || !t) return s === t;
  
  return equalTrees(s, t) || 
         isSubtree(s.left, t) || 
         isSubtree(s.right, t);
};

const a = {
  val: 1,
  left: {
    val: 2,
    right: {val: 3}
  },
  right: {
    val: 4,
    left: {val: 5},
    right: {val: 6}
  }
};
const b = {
  val: 4,
  left: {val: 5},
  right: {val: 6}
};
const c = {
  val: 4,
  left: {val: 42}, // wrong val
  right: {val: 6}
};
const d = {
  val: 4,
  left: {val: 5},
  right: {
    val: 6,
    left: {val: 7} // extra child
  }
};

console.log(isSubtree(a, b));
console.log(isSubtree(a, c));
console.log(isSubtree(a, d));
0 голосов
/ 09 мая 2020

После просмотра комментариев выше я пришел к следующему решению:

const isSubtree = (s, t) => {
    if (!s) return false
    if (!t) return true
    if (s.val === t.val) {
        if (isSameTree(s, t)) return true
    }
    return isSubtree(s.left, t) || isSubtree(s.right, t)
}

const isSameTree = (c, t) => {
    if (!c && !t) return true
    if (!c || !t) return false
    if (c.val !== t.val) return false
    return isSameTree(c.left, t.left) && isSameTree(c.right, t.right)
}
...