트리는 자주 나오는 기호를 더 싸게 만든다.
가중치가 있는 잎으로 이진 부호 트리를 만들면 자주 나오는 기호는 더 짧은 경로를 받고 같은 표현으로 인코딩과 디코딩을 모두 수행할 수 있습니다.
가중치가 있는 트리 하나가 우리가 쓰는 비트와 다시 복원하는 기호를 어떻게 함께 결정할까요?
- 잎 하나에 기호 하나와 빈도 가중치 하나 표현하기
- 가장 가벼운 노드 두 개를 되풀이해 합쳐 부호 트리 만들기
- 루트에서 가지의 기호 포함 여부를 따라 기호 인코딩하기
- 비트를 따라 잎에 닿은 뒤 루트에서 다시 디코딩 시작하기
make-leaf-set은 유한한 기호와 가중치를 가벼운 순서대로 정렬합니다. successive-merge는 가장 가벼운 노드 두 개를 꺼내 기호 집합과 가중치를 합친 뒤 새 노드를 다시 그 순서에 넣고 하나의 트리가 남을 때까지 반복합니다. 제공된 가중치에서는 A가 한 비트 경로를 받고 B와 C와 D는 더 깊은 경로를 받습니다.
인코딩은 다음 기호가 왼쪽 가지에 있는지 오른쪽 가지에 있는지 묻고 아래로 내려가기 전에 0이나 1을 기록합니다. 디코딩은 같은 결정을 반대 방향으로 소비합니다. 잎에 닿으면 그 기호를 내보내고 다음 부호를 읽기 위해 루트로 돌아갑니다. 같은 가중치에는 다른 올바른 트리가 있을 수 있지만 여기서는 명시적인 삽입 규칙이 이번 실행의 트리를 고정합니다.
(begin
(define (append-symbols left right)
(if (null? left)
right
(cons (car left) (append-symbols (cdr left) right))))
(define (make-leaf symbol weight)
(list 'leaf symbol weight))
(define (leaf? object) (eq? (car object) 'leaf))
(define (symbol-leaf leaf) (cadr leaf))
(define (weight-leaf leaf) (caddr leaf))
(define (left-branch tree) (car tree))
(define (right-branch tree) (cadr tree))
(define (symbols tree)
(if (leaf? tree)
(list (symbol-leaf tree))
(caddr tree)))
(define (weight tree)
(if (leaf? tree)
(weight-leaf tree)
(car (cdr (cdr (cdr tree))))))
(define (make-code-tree left right)
(list left
right
(append-symbols (symbols left) (symbols right))
(+ (weight left) (weight right))))
(define (adjoin-weighted node nodes)
(cond ((null? nodes) (list node))
((< (weight node) (weight (car nodes)))
(cons node nodes))
(else
(cons (car nodes)
(adjoin-weighted node (cdr nodes))))))
(define (make-leaf-set pairs)
(if (null? pairs)
'()
(let ((entry (car pairs)))
(adjoin-weighted
(make-leaf (car entry) (cadr entry))
(make-leaf-set (cdr pairs))))))
(define (successive-merge nodes)
(if (null? (cdr nodes))
(car nodes)
(successive-merge
(adjoin-weighted
(make-code-tree (car nodes) (cadr nodes))
(cdr (cdr nodes))))))
(define (symbol-in? symbol items)
(cond ((null? items) #f)
((eq? symbol (car items)) #t)
(else (symbol-in? symbol (cdr items)))))
(define (encode-symbol symbol tree)
(if (leaf? tree)
'()
(if (symbol-in? symbol (symbols (left-branch tree)))
(cons 0 (encode-symbol symbol (left-branch tree)))
(cons 1 (encode-symbol symbol (right-branch tree))))))
(define tree
(successive-merge
(make-leaf-set '((A 4) (B 2) (D 1) (C 1)))))
(list (weight tree)
(symbols tree)
(list (encode-symbol 'A tree)
(encode-symbol 'B tree)
(encode-symbol 'C tree)
(encode-symbol 'D tree))))- 출력
- —
- 값
- —
- 진단
- —
첫 프로그램은 (8 (A B C D) ((0) (1 0) (1 1 0) (1 1 1)))을 반환합니다. 두 번째 프로그램은 ((0 1 1 1 1 0) (A D B))를 반환합니다.
루트를 만드는 가중치 병합을 따라간 뒤 각 기호 포함 검사와 기록되는 가지 비트를 비교하세요. 디코딩에서는 같은 비트가 가지를 고르는 과정을 따라가고 잎이 기호를 내보낼 때마다 루트에서 다시 시작하는지 확인하세요. 제한된 실행 흐름은 이번 고정 트리와 메시지만 설명합니다.
힌트를 보기 전에 프로그램을 바꿔 보세요.
제공된 트리로 (A B C D A)를 인코딩하세요. 실행하기 전에 전체 비트 수를 예상하고 A가 C나 D보다 적은 비트를 쓰는 이유를 설명하세요.
힌트 하나 보기
첫 예제에서 각 부호를 읽고 다섯 경로를 이어 붙인 뒤 가지 선택 횟수를 세어 보세요.