Путь в двоичном дереве - PullRequest
0 голосов
/ 17 января 2019

Я готовлюсь к экзаменам и почти все, но это упражнение мне не понятно. Как создать функцию, которая будет искать путь в двоичном дереве по определенному значению.

Вот пример:

(define tree '(1 (2 (3) (4)) (5 (6 (7) (8)) (9))))
(find-path tree 4)
(1 2 4)

1 Ответ

0 голосов
/ 18 января 2019

Я начинаю набрасывать какой-то код -

(define (find t q) ;; find q in t

(path empty) ;; the return path will be a list, we'll start it off as an empty list

(if (empty? t) ;; fundamental laws: always check for empty list first
    #f         ;; if the tree is empty, there is nothing to find, we use #f to signal this
    (if (eq? q (car t)) ;; otherwise we can check if the node matches q ...
        ;; wups we can't do eq? test yet, it's possible `(car t)` is a list of nodes
        ))

Как мне это увидеть? Я смотрю на наш список ввода -

(define tree '(1 (2 (3) (4)) (5 (6 (7) (8)) (9))))
  1. Мы всегда проверяем empty? сначала
  2. Если список не пустой, мы знаем, что имеем:
    • хотя бы один элемент, (car tree)
    • остальные элементы, (cdr tree)
  3. Я визуализирую элементы внешнего списка; их всего три:
    • 1
    • (2 (3) (4))
    • (5 (6 (7) (8)) (9))
  4. Первый элемент был 1, поэтому я подумал, что смогу достать eq? и проверить, соответствует ли он q сразу
  5. Я заметил, что второй элемент был другого типа . Интуитивно понятно, что мы не можем сопоставить один элемент со списком элементов, поэтому мы должны обработать list? case до того, как попробуем eq?

Исправь мою бу бу -

(define (find t q)

(path empty)

(if (empty? t)
    #f
    (if (list? (car t))
        ;; when the node is a list of nodes
        (if (eq? q (car t))
            ;; when the node matches q
            ;; when the node does not match q
            )))

Свернуть цепочки if до cond для лучшей читаемости -

(define (find t q)

(path empty)

(cond ((empty? t)
        #f)
      ((list? (car t))
       ;; when the node is a list of nodes
       )
      ((eq? q (car t))
       ;; when the node matches q
       )
      (else
       ;; when the node does not match q
       ))

Код теперь более плоский и приятный для чтения. Некоторые из этих пробелов сложно заполнить, но меня тянет ко второму пробелу; когда q равно (car t), это означает, что мы нашли совпадение, и пришло время вернуть path -

(define (find t q)

(path empty)

(cond ((empty? t)
        #f)
      ((list? (car t))
       ;; when the node is a list of nodes
       ;; we'll come back to this ...
       )
      ((eq? q (car t))
       (cons q path)) ;; return the path with the final node
      (else
       ;; when the nodes does not match q
       ;; and save this for later too ...
       ))

Хорошо, это было не так уж плохо. Поэтому я проверил, когда (car t) соответствует q, теперь я должен сказать, что происходит, когда он не совпадает. Когда (car t) не совпадает, я добавлю его в path и каким-то образом проверю, соответствует ли q любому из дочерних узлов, (cdr t) -

(define (find t q)

(path empty)

(cond ((empty? t)
        #f)
      ((list? (car t))
       ;; when node is a list of nodes
       ;; we'll come back to this ...
       )
      ((eq? q (car t))
       (cons q path))
      (else

       ;; add the node to the path ...
       (cons (car t) path)

       ;; check the node's children for a match
       (find (cdr t) q)

       ;; this doesn't quite work ...
       ))

Я сталкиваюсь с ситуацией, когда нам нужно обновить path новым узлом, и мне нужно вызвать find, у которого нет параметра path. Чтобы исправить это, я ввел цикл, который позволяет нам повторно вычислять выражение с любыми указанными нами аргументами -

(define (find t q)

(let loop ;; lazily and sloppily insert a named loop

((path empty) ;; initialize the parameters that will change
 (t t))

(cond ((empty? t) ;; the expression to repeat, (cond ...)
        #f)
      ((list? (car t))
       ;; when the node is a list of nodes
       )
      ((eq? q (car t))
       (cons q path))
      (else
       (loop (cons (car t) path) ;; updated path
             (cdr t))))          ;; updated tree

Предложение else научило меня сопоставлять дочерние узлы, представляющие собой список узлов. Это, безусловно, облегчит работу с последним пробелом в коде, что и нужно делать, когда узел представляет собой список узлов! -

(define (find t q)

(let loop

((path empty)
 (t t))

(cond ((empty? t)
        #f)
      ((list? (car t))

       ;; we could just recur the loop with 
       (loop path
             (car t))

       ;; but what about (cdr t) in this case?
       (loop path
             (cdr t))

      ((eq? q (car t))
       (cons q path))
      (else
       (loop (cons (car t) path)
             (cdr t))))

Последняя проблема здесь - у меня есть два (2) списка для проверки; (car t) определяется как список, а (cdr t) - список. Я должен проверить их обоих. Простое решение - объединить два loop вызова с or. Если один loop вернет #f, другой будет проверен -

(define (find t q)

(let loop

((path empty)
 (t t))

(cond ((empty? t)
        #f)
      ((list? (car t))
       (or (loop path   ;; or represents dysjunction!
             (car t))
           (loop path
             (cdr t))))
      ((eq? q (car t))
       (cons q path))
      (else
       (loop (cons (car t) path)
             (cdr t))))

Исправить скобки, запустить автоматический индентор -

(define (find t q)
  (let loop
    ((path empty)
     (t t))
    (cond ((empty? t)
           #f)
          ((list? (car t))
           (or (loop path
                     (car t))
               (loop path
                     (cdr t))))
          ((eq? q (car t))
           (cons q path))
          (else
           (loop (cons (car t) path)
                 (cdr t))))))

(define tree '(1 (2 (3) (4)) (5 (6 (7) (8)) (9))))

(find tree 4)
;; '(4 2 1)

(find tree 8)
;; (8 6 5 1)

(find tree 9)
;; (9 5 1)

Заметьте, что результат обратный, потому что действительно path построен в обратном порядке. Условие выхода, которое возвращает путь, просто требует вызова reverse перед возвратом -

(define (find t q)
  (let loop
    ((path empty)
     (t t))
    (cond ((empty? t)
           #f)
          ((list? (car t))
           (or (loop path
                     (car t))
               (loop path
                     (cdr t))))
          ((eq? q (car t))
           (reverse (cons q path))) ;; don't forget to reverse!
          (else
           (loop (cons (car t) path)
                 (cdr t))))))

(define tree '(1 (2 (3) (4)) (5 (6 (7) (8)) (9))))

(find tree 4)
;; '(1 2 4)

(find tree 8)
;; (1 5 6 8)

(find tree 9)
;; (1 5 9)
...