Tải bản đầy đủ (.pdf) (60 trang)

Tài liệu Cấu trúc dữ liệu_chương 5 docx

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (703.36 KB, 60 trang )




1



CHNG V. CU TRÚC D LIU

a bit rng vic lp trình ph thuc phn ln vào cách mô hình hoá d liu ca bài toán
cn gii. Mi quan h gia thut toán và cu trúc d liu trong lp trình đã đc
Niclaus Wirth, tác gi ngôn ng lp trình Pascal, đa ra mt công thc rt ni ting :
T
Cu trúc d liu + Thut toán = Chng trình
(Data structure + Algorithms = Programs)
Nh đã thy, thut toán mi ch phn ánh các thao tác cn x lý, còn đi tng đ x lý
trong máy tính li là d liu. Nói đn thut toán là nói đn thut toán đó tác đng lên d liu
nào. Còn nói đn d liu là nói đn d liu y cn đc tác đng bi thut toán nào đ đa
đn kt qu mong mun. D liu biu din thông tin cn thit đ gii bài toán, gm d liu
đa vào, d liu đa ra và d liu tính toán trung gian. Mt cu trúc d liu liên quan đn ba
yu t : kiu d liu, các phép toán tác đng lên d liu và cách biu din d liu trong b
nh ca máy tính tu theo công c lp trình.
Trong chng này, chúng ta s trình bày mt s cu trúc d liu tiêu biu nh tp hp,
ngn xp, danh sách móc ni, cây...
V.1 Tp hp
Trong toán hc, tp hp (set) là mt nhóm hay mt b su tp các đi tng
1
phân bit
{x
1
, x


2
, …, x
n
}, đc gi là các phn t (elements) ca tp hp. Do mi phn t ca mt tp
hp ch đc lit kê mt ln và không đc sp xp th t nên ngi ta không nói đn phn
t th nht, phn t th hai, v.v... Ví d hai tp hp sau đây là đng nht :
{ a, b, d, a, d, e, c, d } = { a, b, c, d }
Do tp hp cng là mt danh sách, ngi ta có th s dng cu trúc danh sách đ biu
din tp hp trong Scheme. Nh vy, mt tp hp rng là mt danh sách rng.  so sánh hai
tp hp có bng nhau không, ta có th s dng v t equal? nh sau :
(define (setequal? E1 E2)
(cond ; hai tp hp rng thì bng nhau
((and (null? E1) (null? E2)) #t)
; hai tp hp có hai phn t đu tiên bng nhau


1
Khái nim đi tng ca tp hp có tính trc giác, do nhà toán hc ngi c G. Cantor đa ra t nm 1985.
n nm 1902, nhà trit hc ngi Anh B. Russell đã ch ra nhng nghch lý toán hc (paradox) hay nhng
mâu thun lôgic trong lý thuyt tp hp.
147
148 LP TRÌNH HÀM
((equal? (car E1) (car E2))
(setequal? (cdr E1) (cdr E2)))
; hai tp hp có hai phn t đu tiên khác nhau thì khác nhau !!!
(else #f)))
(setequal? ’(1 3 4 5 6) ’(1 3 4 5 6))
--> #t
hoc :
(define E1 ’(a b c d e))

(define E2 E1)
(setequal? E1 E2)
--> #t
 ý rng trong v t setequal? trên đây, ta cha x lý hai tp hp có các phn t
ging y nhau và có cùng s phn t, nhng không đc sp xp th t nh nhau :
(setequal? '(1 5 4 3 6) '(1 3 4 5 6))
--> #f
Sau đây ta s s dng thng xuyên hàm member đ x lý tp hp. Hàm này kim tra
mt phn t có thuc mt danh sách đã cho hay không :
; Trng hp phn t kim tra có kiu đn gin
(member ’c ’(a b c d e))
--> ’(c d e)
; Trng hp phn t kim tra có kiu phc hp
(member (list ’a) ’(b (a) c))
--> ’((a) c)
V t in? kim tra mt phn t có thuc mt tp hp đã cho hay không ?
(define (in? x E)
(cond ((null? E) #f) ; danh sách rng
((member x E) #t) ; x là phn t kiu đn gin
(else (in? x (cdr E))))) ; x là phn t kiu phc hp
(in? ’c E1)
--> #t
 xây dng mt tp hp t mt danh sách, ngi ta phi loi b các phn t trùng lp.
Hàm list->set sau đây s dng hàm member ln lt kim tra các phn t ca danh
sách đã cho. Kt qu tr v ch gi li phn t cui cùng đi vi nhng phn t trùng nhau và
cha sp xp li các phn t theo th t (xem phng pháp sp xp nhanh  cui chng).
(define (list->set L)
(cond ((null? L) ’())
((member (car L) (cdr L))
(list->set (cdr L)))

(else (cons (car L)
(list->set (cdr L)))))
(list->set ’(a b d a d e c d))
--> ’(b a e c d)
(define (union2 E1 E2)
(cond ((null? E1) E2)
CU TRÚC D LIU 149
((member (car E1) E2) (union2 (cdr E1) E2))
(else (cons (car E1) (union2 (cdr E1) E2)))))
1. Phép hp trên các tp hp
Gi s cho hai tp hp E
1
và E
2
, ta cn tìm kt qu ca phép hp ca hai tp hp E
1
∪E
2

mt tp hp nh sau :
(define (union2 E1 E2)
(cond ; tp hp th nht là rng thì kt qu là tp hp th hai
((null? E1) E2)
; nu tp hp th nht có phn t thuc tp hp th hai thì b qua
((member (car E1) E2) (union2 (cdr E1) E2))
; tip tc sau khi gim kích thc tp hp th nht
(else (cons (car E1) (union2 (cdr E1) E2)))))
(union2 ’(1 2 3 7) ’(2 3 4 5 6))
--> ’(1 7 2 3 4 5 6)
M rng phép hp ca hai tp hp, ta xây dng phép hp trên các tp hp bt k bng

cách s dng hàm list-it đã đc đnh ngha  chng trc :
(define (union . Lset)
(list-it union2 Lset ’()))
(union ’(1 2 3 4) ’(2 3 4 5 6) ’(4 5 6 7) ’(6 7 8))
--> ’(1 2 3 4 5 6 7 8)
2. Phép giao trên các tp hp
Tng t cách xây dng phép hp trên các tp hp bt k, trc tiên ta xây dng phép
giao ca hai tp hp E
1
∩E
2
nh sau :
(define (intersection2 E1 E2)
(cond ; nu mt tp hp là rng thì kt qu cng rng
((null? E1) E1)
; chn ra phn t nm  c hai tp hp
((member (car E1) E2) (cons (car E1)
(intersection2 (cdr E1) E2)))
; tip tc sau khi gim kích thc tp hp th nht
(else (intersection2 (cdr E1) E2))))
(intersection2 ’(1 2 3 4)’(2 3 4 5 6))
--> ’(2 3 4)
M rng phép giao ca hai tp hp, ta xây dng phép giao trên các tp hp bt k bng
cách s dng hàm apply đã đc đnh ngha  mc Error! Reference source not found. :
(define (intersection . Lset)
(cond ; giao ca các tp hp rng cng là rng
((null? Lset) ’())
; giao ca mt tp hp là chính nó
((null? (cdr Lset)) (car Lset))
; đa v thc hin phép giao ca hai tp hp

(else (intersection2
(car Lset)
(apply intersection (cdr Lset))))))

150 LP TRÌNH HÀM
(intersection ’(1 2 3 4) ’(2 3 4 5 6) ’(4 5 6 7))
--> ’(4)
3. Phép hiu ca hai tp hp
Cho hai tp hp E
1
và E
2
, ta âënh nghéa phép hiu ca hai tp hp E
1
\E
2
nh sau :
(define (difference E1 E2)
(cond ; nu E
2
rng thì kt qu là E
1
((null? E2) E1)
; nu E
1
rng thì kt qu là rng
((null? E1) ’())
; nu E
1
có phn t thuc E

2
thì b qua
((member (car E1) E2)
(difference (cdr E1) E2))
; tip tc sau khi gim kích thc tp hp E
1
(else (cons (car E1)
(difference (cdr E1) E2)))))
(difference ’(1 2 3 4 5) ’(1 2))
--> ’(3 4 5)
4. Tìm các tp hp con ca mt tp hp
Cho trc tp hp E, ta cn tìm tp hp tt c các tp hp con (sub-set) ca E, ký hiu 2
E
.
Chng hn cho E={ a, b, c } thì 2
E
={

, {a}, {b}, {c}, {a, b}, {a, c}, {b, c}, {a, b, c}} có tt c
8 phn t bao gm tp hp rng và bn thân tp hp E.
Nu tp hp E rng, thì 2
E
cng rng. Nu E khác rng, xét mt phn t a

E, khi đó có
các tp hp con cha a và các tp hp con không cha a. u tiên xây dng tp hp E\{a},
sau đó, chèn a vào tt c các tp hp con ca E\{a}. Ta có hàm subset nh sau :
(define (subset E)
(if (null? E)
(list ’())

(let ((Lremain (subset (cdr E))))
(append Lremain
(map (lambda (L)
(cons (car E) L))
Lremain)))))
(subset ’(a b c))
--> ’(() (c) (b) (b c) (a) (a c) (a b) (a b c))
V.2 Ngn xp
Danh sách kiu «ngn xp» (stack), còn đc gi là «chng» (tng t mt chng đa,
mt bng đn...), là mt cu trúc d liu mà phép b sung hay loi b mt phn t luôn luôn
đc thc hin  mt đu gi là đnh (top). Nguyên tc hot đng «vào sau ra trc» ca
ngn xp đã dn đn mt tên gi khác là danh sách kiu LIFO (Last In First Out).
Ngn xp đc s dng rt ph bin trong tin hc. Hình V.1. minh ho hot đng ca mt
ngn xp khi thc hin th tc đ quy tính n!.
CU TRÚC D LIU 151
V.2.1. Kiu d liu tru tng ngn xp
Sau đây là đc t cu trúc d liu tru tng kiu ngn xp :
Types Stack(T)
functions
empty-stack : → Stack(T)
empty-stack? : Stack(T) → boolean
push-stack : T × Stack(T) → Stack(T)
pop-stack : Stack(T) -/→ Stack(T)
top-stack : Stack(T) -/→ T
preconditions
pop-stack(s: Stack(T))
ch xác đnh khi và ch khi (not empty-stack?(s))
top-stack(s: Stack(T))
ch xác đnh khi và ch khi (not empty-stack?(s))
axioms

var x: T, s : Stack(T)
empty-stack?(empty-stack) = true
empty-stack?(push-stack (x, S)) = false
pop-stack(push-stack (x, S)) = S
top-stack(push-stack (x, S)) = x

fac(3)= ? fac(3)=3*fac(2) fac(2)=2*fac(1) fac(1)=1*fac(0)

fac(1)= 1 fac(2)=2*1=2 fac(3)= 3*2 fac(3)= 6
fac(3)=?
3*fac(2)
2*fac(1)
1*fac(0)
fac(0)=1
3*fac(2)
2*fac(1)
fac(1)=?
3*fac(2)
fac(2)=?
3*fac(2)
2*fac(1)
1*1
3*fac(2)
2*1
6
3*2
Hình V.1. Hot đng ca ngn xp thc hin th tc đ quy tính n!.
Ta cn xây dng các hàm Scheme thao tác trên ngn xp nh sau :
(empty-stack? (empty-stack))
-- #t


152 LP TRÌNH HÀM
(empty-stack? (push-stack x S))
-- #f
(pop-stack (push-stack x S))
--> S
(top-stack (push-stack x S)
--> x
V.2.2. Xây dng ngn xp
Có nhiu cách đ biu din cu trúc d liu kiu ngn xp trong Scheme. Phng pháp t
nhiên hn c là biu din ngn xp di dng mt danh sách mà các thao tác b sung và loi
b mt phn t đc thc hin  mt đu danh sách. Trong Scheme, mi ln b sung mt
phn t vào danh sách kéo theo vic to ra mt b đôi (dotted pair) mi.

S ...

$
a)
b)
S

$
Hình V.2. Hot đng b sung (a) và loi b (b) mt phn t ca ngn xp.
Ta có các hàm Scheme nh sau :
(define (empty-stack) ’())
(define empty-stack? null?)
(define (push-stack x S)
(cons x S))
(define (pop-stack S)
(if (empty-stack? S)

(display ”ERROR: stack is empty!”)
(cdr S)))
(define (top-stack S)
(if (empty-stack? S)
(display ”ERROR: stack is empty!”)
(car S)))
Ta thy các thao tác trên ngn xp tng t đi vi danh sách. Sau đây là mt s ví d
minh ho thao tác trên ngn xp s dng các hàm đã có phù hp vi ca1c tiên đ trong đc t
trên đây :
(empty-stack? (push-stack 'a (empty-stack)))
--> #f
(pop-stack (push-stack ’a ’(1 2 3)))
--> ’(1 2 3)
CU TRÚC D LIU 153
(pop-stack (push-stack ’a ’(1 2 3)))
--> ’(1 2 3)
(top-stack (push-stack ’a ’(1 2 3)))
--> ’a
(top-stack (pop-stack
(push-stack 1 (pop-stack
(push-stack 2 (push-stack 3 (pop-stack
(push-stack 4 (push-stack 5
(empty-stack))))))))))
--> 3
V.2.3. Xây dng trình son tho vn bn
Sau đây là mt ví d đn gin minh ho ng dng ngn xp đ xây dng mt trình son
tho đn gin cho phép thc hin vào ra tng dòng vn bn mt. Hot đng nh sau : hàm
nhn dòng vào là mt tham bin đ lu gi trong mt vùng nh trung gian (buffer). Dòng vào
cha các ký t hn hp mà mt ký t nào đó có th là mt lnh son tho (edit command).
Có bn loi ký t nh sau :

• Các ký t khác ba ký t #, $ và newline đu là ký t vn bn đ đc lu gi trong
vùng nh trung gian.
• Ký t # dùng đ xoá ký t đng trc trong vùng nh trung gian.
• Ký t $ dùng đ xoá tt c ký t trong vùng nh trung gian.
• Ký t qua dòng newline đ kt thúc mt dòng vào và đa ni dung vùng nh trung
gian lên màn hình.
Ta s s dng mt ngn xp đ biu din vùng nh trung gian. Trong chng trình có s
dng mt hàm cc b loop đ đc các ký t liên tip t dòng vào. Ch s i là ký t đang đc
th i.
(define (lineeditor str)
(let ((L (string-length in-str)))
(letrec ((loop
(lambda (i buffer)
(if (< i L)
(let ((readchar (string-ref in-str i)))
(case readchar
((#\#) (loop (+ i 1) (pop-stack buffer)))
((#\$) (loop (+ i 1) (empty-stack)))
((#\.) (map (display (reverse buffer)))
(else (loop (+ i 1)
(begin
(display "ERROR: dot is the end of
command!")
(newline))))))
(loop 0 (empty-stack)))))
(lineeditor "XY$abce#def.")
--> abcdef

154 LP TRÌNH HÀM
Ngi đc có th phát trin trình son tho trên đây đ x lý các tình hung son tho vn

bn phc tp hn.
V.2.4. Ngn xp đt bin
Ta mun rng hot đng ca ngn xp gn gi vi các chng (pile) hn, ngha là vic b
sung loi b phn t ch liên quan đn mt chng đ ta có th gán cho bin. Ta xây dng kiu
d liu tru tng ngn xp đt bin (mutable stack) Stack!(T) (sau tên có mt du chm
than) nh sau :
Types Stack!(T)
functions
empty-stack! : → Stack!(T)
empty-stack!? : Stack!(T) → boolean
push-stack! : T × Stack!(T) → Stack!(T)
pop-stack! : Stack!(T) -/→ T
top-stack! : Stack!(T) -/→ T
 phân bit vi các ngn xp đã xét, ta thêm vào sau các tên kiu và hàm mt du chm
than ! tng t các đt bin. im hot đng khác bit ca ngn xp đt binlà sau khi loi
b mt phn t thì hàm tr v phn t trên đnh.
Trong chng trc, khi xét môi trng làm vic ca Scheme, ta thy rng không th s
dng lnh set! đ thay đi ni dung ca ngn xp chng hn nh :
(define (push-stack x s)
(set! s (cons x s)) s)
Ta phi áp dng tính cht đt bin ca các danh sách, bng cách biu din ngn xp rng
(empty stack) bi mt danh sách khác rng đ sau đó thay đi bi cdr.
(define (empty-stack!) (list ’stack!))
(define (empty-stack!? S)
(and (pair? S)
(null? (cdr S))
(eq? ’stack! (car S))))
(define S1 (empty-stack!))
S1 ; xem ni dung ca ngn xp
--> ’(stack!)

(empty-stack!? S1)
--> #t
 b sung (push) và loi b (pop) trên ngn xp S, ta gi thit rng bin S luôn luôn tr
đn cùng mt b đôi. Tt c các thao tác vt lý đc thc hin nh lnh cdr nh minh ho
trong hình di đây.
Lúc ngn xp rng, danh sách S ch cha phn t ’stack!. Sau khi b sung phn t
’bob thì phn t này đng th hai (ngay sau ’stack!) trong danh sách S.
Ta xây dng hàm nh sau :
(define (push-stack! x S)
(set-cdr! S (cons x (cdr S))))
CU TRÚC D LIU 155
(push-stack! ’ann S1)
(push-stack! ’bob S1)
(push-stack! ’jim S1)

Hình V.3. Hot đng b sung mt phn t vào ngn xp.


’stack!
’bob
S ...
$
Ta tip tc xây dng hàm xem và loi b phn t đnh ca ngn xp :
(define (top-stack! S)
(if (empty-stack!? S)
(begin
(display "ERROR: stack! is empty!")
(newline))
(cadr S)))
(top-stack! S1)

--> 'jim
(define (pop-stack! S)
(if (empty-stack!? S)
(begin
(display "ERROR: stack! is empty!") (newline))
(let ((top (cadr S)))
(set-cdr! S (cddr S))
top)))
(pop-stack! S1)
--> 'jim
(top-stack! S1)
--> ’bob
Thao tác vt lý phép loi b đc minh ho nh sau :

Hình V.4. Hot đng loi b mt phn t khi ngn xp.

’stack!

S ...
$
Sau đây là mt cách khác s dng k thut truyn thông đip đ xây dng các hàm x lý
cu trúc d liu kiu ngn xp trong Scheme.
(define (make-empty-stack!)
(let ((content '()))
(lambda (message . Largt)
(case message

156 LP TRÌNH HÀM
((empty-stack!?) (null? content))
((push-stack!)

(set! content
(cons (car Largt) content)) content)
((pop-stack!)
(if (null? content)
(begin
(display "ERROR: stack is empty!")
(newline))
(let ((top (car content)))
(set! content (cdr content))
top)))
((top-stack!)
(if (null? content)
(begin
(display "ERROR: stack is empty!")
(newline))
(car content)))))))
; Kim tra ngn xp rng nh thông đip ’empty-stack!?
(define (empty-stack!? S) (S ’empty-stack!?))
; B sung mt phn t vào ngn xp nh thông đip ’push-stack!
(define (push-stack! x S) (S ’push-stack! x))
; Loi b mt phn t khi ngn xp nh thông đip ’pop-stack!
(define (pop-stack! S) (S ’pop-stack!))
; X lý phn t đnh ca ngn xp nh thông đip ’top-stack!
(define (top-stack! S) (S ’top-stack!))
; nh ngha mt ngn xp mi
(define S2 (make-empty-stack!))
(empty-stack!? S2)
--> #t
(push-stack! ’ann S2)
--> ’(ann)

(push-stack! ’bob S2)
--> ’(bob ann)
(push-stack! ’jim S2)
--> ’(jim bob ann)
(top-stack! S2)
--> ’jim
(pop-stack! S2)
--> ’jim
(top-stack! S2)
--> ’bob
V.2.5. Tính biu thc s hc dng hu t
Mt biu thc s hc thng đc vit di dng trung t, chng hn :
CU TRÚC D LIU 157
(9 2) * 52+

Tuy nhiên, trong b nh, các biu thc đc biu din dng cây (ta s xét cu trúc cây
trong mc tip theo). Khi tính toán, biu thc s hc dng cây nh phân (cho các phép toán
hay hàm, gi chung là toán t, có ti đa hai toán hng) đc đa ra thành mt chui ký t
dng hu t (dng ký pháp Ba Lan) nh sau :
9 2 52 * +

Lúc này các du ngoc không còn na do các biu thc dng hu t thng không nhp
nhng (un-ambiguous). Khi đc biu thc t trái qua phi, ta gp các toán hng trc khi gp
mt toán t. Nu áp dng toán t này cho các toán hng trc đó, ta nhn đc mt kt qu
là mt toán hng mi và quá trình tip tc theo đúng ch đ «vào trc ra sau». Do vy,
ngi ta s dng mt ngn xp đ mô phng quá trình tính toán biu thc : các toán hng
(đc đc t biu thc hu t, ln lt t trái qua phi) đc «đy vào» ngn xp cho đn khi
gp toán t. Thc hin phép toán vói các toán hng «ly ra» t ngn xp ri li đy kt qu
vào ngn xp cho đn khi ht biu thc và ly kt qu nm  đnh ngn xp.
Sau đây ta xây dng hàm tính mt biu thc s hc dng hu t (ngi đc có th tìm đc

các giáo trình «Cu trúc d liu và thut toán» đ hiu chi tit hn). Chng trình s dng
ngn xp đt bin x lý các phép toán s hc +, -, *, / và hàm sqrt :
(define (evaluation postfix-expr)
(let ((pile (make-empty-stack!)))
(letrec ((compute
(lambda (exp)
(if (null? exp)
(top-stack! pile)
(let ((s (car exp)))
(if (number? s)
(push-stack! s pile)
(begin
(case s
((sqrt)
(let ((v (pop-stack! pile)))
(push-stack! (sqrt v) pile)))
((+)
(let ((v2 (pop-stack! pile))
(v1 (pop-stack! pile)))
(push-stack! (+ v1 v2) pile)))
((*)
(let ((v2 (pop-stack! pile))
(v1 (pop-stack! pile)))
(push-stack! (* v1 v2) pile)))
((-)
(let ((v2 (pop-stack! pile))
(v1 (pop-stack! piler)))
(push-stack! (- v1 v2) pile)))
((/)
(let ((v2 (pop-stack! pile))

(v1 (pop-stack! pile)))
(push-stack! (/ v1 v2) pile))))
(compute (cdr exp)))))))))
(compute postfix-expr))))

158 LP TRÌNH HÀM
(evaluation ’(9 2 + 52 sqrt *))
--> 79.3221
V.3 Tp
V.3.1. Cu trúc d liu tru tng kiu tp
Cu trúc d liu kiu tp (file) mô phng mt hàng đi (queue) có ch đ hot đng theo
kiu «vào trc ra trc» FIFO (First In First Out). Lúc này phép b sung đc thc hin 
cui hàng (đuôi), và phép loi b đc thc hin  đu hàng :

Loi b B sung
u hàng Cui hàng


Hình V.5. Mô phng cu trúc d liu kiu tp tng t mt hàng đi.
Ta xây dng File(T) cu trúc d liu tru tng kiu tp có các phn t kiu T bt k ca
Scheme nh sau :
Types File(T)
functions
empty-file : → File(T)
empty-file? : File(T) → boolean
push-file : T × File(T) → File(T)
pop-file : File(T) → File(T)
top-file : File(T) → T
axioms
var s: T, F: File(T)

empty-file?(empty-file) = true
empty-file?(push-file (s, F)) = false
top-file(push-file (s, F)) =
if empty-file?(F) then s else top-file(F))
pop-file(push-file(s, F) =
if empty-file?(F)
then empty-file
else push-file(s, pop-file(F))
Sau đây ta xây dng các hàm x lý tp s dng kiu danh sách ca Scheme. Vic b sung
phn t đc thc hin  cui danh sách và vic loi b thc hin  đu danh sách.
(define (empty-file) ’())
(define empty-file? null?)
(define (push-file s F)
(append F (list s)))
(define (pop-file F)
(if (empty-file? F)
CU TRÚC D LIU 159
(begin
(display "ERROR: file is empty!")
(newline))
(cdr F)))
(define (top-file F)
(if (empty-file? F)
(begin
(display "ERROR: file is empty!")
(newline))
(car F)))
Ta nhn thy rng vic s dng danh sách là không hp lý khi kích thc tp tng lên và
lnh appenf trong hàm push-file s gây ra chi phí tính toán ln.
V.3.2. Ví d áp dng tp

Sau đây ta xét mt ví d áp dng cu trúc d liu kiu tp va xây dng. Gi s cho trc
mt danh sách L gm các s nguyên tng ngt và mt s nguyên N, ta cn tính s lng các
cp s (x, x + N) vi x và N có mt trong danh sách.
Chng hn nu L = (0 2 3 4 6 7 8 9 10 12 13 14 16 20) và N = 8, thì ta tìm đm đc bn
cp s tho mãn nh sau : (2, 10), (4, 12), (6, 14) và (12, 20).
Phng pháp đn gin nht có tính trc quan là vi mi s x ly t L, tin hành tìm kim
các s x + N trong L . Tuy nhiên phng pháp này có chi phí ln vì ta phi duyt qua duyt
li nhiu ln danh sách L. Sau đây là phng pháp ch cn duyt mt ln danh sách L.
Ta s dng tp F có đ dài < N và kim tra mi phn t x ca F :
• Nu tp F rng thì ta ch vic thêm vào phn t x.
• Nu top là phn t đu tp và x > top + N, ta chc chn rng ta không th còn tìm đc
phn t y nào ca F mà y = top + N và do vy ta phi loi b F.
• Nu x = top + N, ta loi b x khi L đ thêm x vào F và đm cp tho mãn (x, top).
• Nu x < top + N, ta ch loi b x khi L đ thêm x vào F.
Chng trình Scheme nh sau :
(define (pair-count L N)
(letrec ((count
(lambda (L result F)
(cond ((null? L) result)
((empty-file? F)
(count
(cdr L)
result
(push-file (car L) F)))
(else
(let
((difference (- (car L) (top-file F))))
(cond
((< N difference)
(count L result (pop-file F)))

((= N difference)

160 LP TRÌNH HÀM
(count
(cdr L)
(+ result 1)
(pop-file (push-file (car L) F))))
(else
(count
(cdr L)
result
(push-file (car L) F))))))))))
(count L 0 (empty-file))))
(pair-count '(0 2 3 4 6 7 9 10 12 13 14 16 20) 8)
--> 4
V.3.3. Tp đt bin
Gi s gi File !(T) cu trúc d liu kiu tp đt bin (mutable file), ta đc t kiu tru
tng nh sau :
Types File!(T)
functions
empty-file : → File!(T)
empty-file!? : File!(T) → boolean
push-file! : T × File!(T) → File!(T)
pop-file! : File!(T) → File!(T)
top-file! : File!(T) -→ T
axioms
var s: T, F: File!(T)
empty-file!?(empty-file!) = true
empty-file!?(push-file! (s, F)) = false
top-file!(push-file! (s, F)) =

if empty-file!?(F) then s else top-file!(F))
if empty-file!?(F)
then pop-file!(push-file!(s, F) = s
if not (empty-file!?(F))
then pop-file!(push-file!(s, F) =
push-file!(s, pop-file!(F))
Ta có th áp dng k thut xây dng các ngn xp đt bin cho cu trúc d liu kiu tp
đt bin. Ta s không s dng lnh append đ b sung phn t mi vào tp mà s dng mt
con tr tr đn đuôi ca tp. Vi cách xây dng này, đ dài tp s không bit gíi hn.
Nh vy, cu trúc tp có dng mt b đôi (dotted pair) mà thành phn car tr đn danh
sách các phn t là ni dung tp, còn thành phn cdr tr đn phn t b đôi cui cùng ca
danh sách này.
(define (empty-file!)
(cons '() '()))
(define (empty-file!? file)
(null? (car file)))
CU TRÚC D LIU 161
(define (push-file! s file)
(let ((doublet (cons s '())))
(if (null? (car file))
(set-car! file doublet)
(set-cdr! (cdr file) doublet))
(set-cdr! file doublet)))

u tp uôi tp
file file
a) Ni dung tp ’(ann bob jim) b) Tp rng


’ann ’bob ’jim


Hình V.6. Hot đng loi b mt phn t khi ngn xp.
(define (top-file! file)
(if (empty-file!? file)
(begin
(display "ERROR: file is empty!")
(newline))
(caar file)))
(define (pop-file! file)
(if (empty-file!? file)
(begin
(display "ERROR: file is empty!")
(newline))
(begin (set-car! file (cdar file)))))
Sau đây là vn dng xây dng tp có ni dung ’(ann bob jim) :
(define F (empty-file!))
(empty-file!? F)
#t
(push-file! ’jim F)
(push-file! ’bob F)
(push-file! ’ann F)
(top-file! F)
--> ’jim
(pop-file! F)
(top-file! F)
--> ’bob


162 LP TRÌNH HÀM
V.4 Cây

Cây (tree) gm mt tp hp nút (node) đc t chc theo kiu phân cp. Nu cây không
có nút nào thì đc gi là cây rng (empty tree).
Khi cây khác rng, nút trên cùng, cao nht, là nút gc (root node). Phía di nút gc có
th có nhiu nút con (child node) ni vi nó bi các cnh (edge). Lúc này, nút gc là nút cha
(father node). Mi nút con li có th là mt nút cha có nhiu nút con ni vi nó, và c th tip
tc. Phn cây to ra t mt nút con bt k là cây con (subtree).
Nhng nút  mc thp nht không có nút con nào ni vi nó là lá (leaf) hay nút ngoài
(exterior node). Mt nút không phi là lá là nút trong (interior node). Các nút con có cùng cha
là các nút anh em (siblings). Mi nút ca cây có mt cha duy nht (tr nút gc), là hu du
hay con cháu (descendant) ca nút gc và là t tiên (ancestor) ca các nút lá. Mt nhánh
(branch) hay mt đng đi (path) là tp hp các nút và các nhánh xut phát t mt nút đn
mt nút khác.
Trong toán hc, cây là mt đ th (graph) liên thông không có chu trình. Trong mt cây,
luôn luôn tn ti duy nht mt đng đi t nút gc đn mt nút bt k, hoc gia hai nút nào
đó đã cho. Nh vy, mt cây có th rng, hoc ch có mt nút gc, hoc gm mt nút gc và
mt hoc nhiu cây con.
Cu trúc cây đc ng dng nhiu trong tin hc : t chc th mc ca các h điu hành
Unix, MS-DOS, ..., mt chng trình Scheme, cây gia ph, ...

Gc
Cnh
Nút trong

(nút ngoài)
Cây con gc b
a
b c d e
f g h
ng đi
t a đn h,

qua e
Hình V.7. Hình nh cây.
Sau đây ta s xét cách Scheme x lý các cu trúc d liu dng cây, trc tiên là cây nh
phân (binary tree), loi cây mà mi nút cha ch có th có ti đa hai nút con.
V.4.1. Cây nh phân
V.4.1.1. Kiu tru tng cây nh phân
Cho trc mt kiu d liu c s T là mt kiu d liu bt k. Cây nh phân kiu T đc
đnh ngha nh sau :
1. 0 (s không) là mt cây nh phân kiu T, đc gi là cây rng.
2. Nu E là mt phn t kiu T, A1 và A2 là nhng cây nh phân kiu T,
thì b ba (E, A1, A2) cng là nhng cây nh phân kiu T .
3. Ch nhng đi tng đc đnh ngha bi hai quy tc trên đây mi là các cây nh phân
kiu T.
CU TRÚC D LIU 163
Nu A = (E, A1, A2) là cây nh phân, thì E là nút gc, A1 là cây con bên trái, A2 là cây
con bên phi ca A. Nu A là cây suy bin ch gm mt nút, thì nút đó luôn luôn là nút gc
ca mt cây con. Ngi ta còn gi A là cây đc gn nhãn (labelled tree, hay tag tree) bi
các giá tr trong T. Hình di đây minh ho hai cây nh phân, mt cây biu din biu thc s
hc x+(y-3*z)/4 và mt cây biu din mt s-biu thc nh sau :

a) x + (y

3*z)/4 b) (define L (cons ’a ’b))
Hình V.8. Hai cây nh phân biu din :
a) biu thc s hc ; b) biu thc Scheme.
Chú ý s lch đi xng ca đnh ngha : nu A không phi là cây rng, thì cây (E, A, 0)
khác vi cây (E, 0, A). Cn phân bit mt nút vi mt giá tr gn cho nút đó. Các nút luôn
luôn ri (phân bit) nhau, còn các giá tr thì có th ging nhau. Ví d, cây biu din s-biu
thc ca Scheme trong hình V.9.(b)  trên có 7 nút, trong khi đó ch có 6 giá tr phân bit
nhau gán cho chúng. Ngi ta đa ra nm loi cây nh phân đc bit nh sau :

x /
+
define
L cons
quote quote
a b
3 z
y *
− 4
1. Cây suy thoái (degenerated hay filiform tree) mi nút ch có đúng mt con không rng.
2. Cây hình lc trái (left comb) có mi con bên phi là mt lá.
Cây hình lc phi (right comb) có mi con bên trái là mt lá.
3. Cây đy đ (complete tree) có mi mc ca cây đu đc làm đy
4. Cây hoàn thin (perfect tree) mi mc ca cây đu đc làm đy, tr mc cui cùng,
nhng các lá là bên trái nht có th.
5. Cây đy đ đa phng mi nút có t 0 đn 2 con.
Gi s BinTree (T) cu trúc d liu kiu cây nh phân, ta đc t kiu tru tng nh sau :
Types BinTree (T)
functions
empty-tree : BinTree(T)
empty-tree? : BinTree(T) → boolean
create-tree : T × BinTree(T) × BinTree(T) → BinTree(T)
root : BinTree(T) → T
left : BinTree(T) → BinTree(T)
right : BinTree(T) → BinTree(T)
leaf? : BinTree(T) → boolean (có th không cn)
preconditions
root(A) ch đc xác đnh nu và ch nu A ≠ empty-tree
left(A) ch đc xác đnh nu và ch nu A ≠ empty-tree
right(A) ch đc xác đnh nu và ch nu A ≠ empty-tree

leaf?(A) ch đc xác đnh nu và ch nu A ≠ empty-tree
axioms
empty-tree?(empty-tree) = true

164 LP TRÌNH HÀM
empty-tree?(create-tree(E, A1, A2)) = false
root(create-tree(E, A1, A2)) = E
left(create-tree(E, A1, A2)) = A1
right(create-tree(E, A1, A2)) = A2
leaf?(A) <=> empty-tree?(left(A)) và empty-tree?(right(A))
V.4.1.2. Biu din cây nh phân
1. Biu din tit kim s dng hai phép cons
; nh ngha cây rng :
(define empty-tree ()) ; hoc
(define empty-tree (list))
; nh ngha v t kim tra cây có rng không :
(define empty-tree? null?)
; nh ngha cây khác rng (E, A1, A2) :
(define (create-tree E A1 A2)
(cons E (cons A1 A2)))
; nh hàm tr v nút gc :
(define root car)
; nh hàm tr v cây con trái :
(define left cadr)
; nh hàm tr v cây con phi :
(define right cddr)
; nh ngha v t kim tra có phi nút lá không :
(define (leaf? A)
(and (null? (cadr A)) (null? (cddr A))))
; Ví d to mt cây nh phân có 3 nút :

(create-tree 1
(create-tree 2 empty-tree empty-tree)
(create-tree 3 empty-tree empty-tree))
--> ’(1 (2 ()) 3 ())
Ví d : Cây A gm 5 nút đc cho nh hình sau đây :

2 3
1
4 5
Hình V.10. Cây nh phân có 5 nút.
đc đnh ngha nh sau :
(create-tree 1
(create-tree 2
(create-tree 4 empty-tree empty-tree)
(create-tree 5 empty-tree empty-tree))
(create-tree 3 empty-tree empty-tree))
--> ’(1 (2 (4 ()) 5 ()) 3 ())
CU TRÚC D LIU 165
2. Biu din dng đy đ
 d dàng x lý khi lp trình, thông thng ngi ta biu din cây dng danh sách, đy
đ các nút, s dng 3 phép cons :
(define empty-tree ())
(define empty-tree? null?)
(define (create-tree E A1 A2)
(list E A1 A2))
(define root car)
(define left cadr)
(define right caddr)
(define (leaf? A)
(and (null? (cadr A)) (null? (caddr A))))

; Ví d to mt cây nh phân có 3 nút :
(create-tree 1 (create-tree 2 empty-tree empty-tree)
(create-tree 3 empty-tree empty-tree))
--> ’(1 (2 () ()) (3 () ()))
Cây A trong Hình V.10. trên đây đc đnh ngha nh sau :
(create-tree 1
(create-tree 2
(create-tree 4 empty-tree empty-tree)
(create-tree 5 empty-tree empty-tree))
(create-tree 3 empty-tree empty-tree))
--> (1 (2) (4 () ()) (5 () ())) (3 () ()))
Trong dng đy đ, mi nút lá A đu đc biu din dng (A () ()).
3. Biu din đn gin
Khi kiu các phn t không cha các b đôi, k c không cha danh sách, mt lá có th
đc biu din đn gin bi giá tr ca nút, mà không cn cons. Ta đnh ngha li nh sau :
(define empty-tree ())
(define empty-tree? null?)
(define (create-tree E A1 A2)
(if (and (null? A1) (null? A2))
E
(list E A1 A2)))
; hay (cons E (cons A1 A2)) tit kim hn
(define (root A)
(if (pair? A)
(car A )
A))
(define (left A)
(if (pair? A)
(cadr A)
()))

(define (right A)

166 LP TRÌNH HÀM
(if (pair? A)
(caddr A)
;hay (cadr A) trong trng hp biu din tit kim
()))
(define (leaf? A)
(not (pair? A)))
; Ví d to mt cây nh phân có 3 nút :
(create-tree 1
(create-tree 2 empty-tree empty-tree)
(create-tree 3 empty-tree empty-tree))
--> ’(1 2 3)
Cây A trong Hình V.10. trên đây đc đnh ngha nh sau :
(create-tree 1
(create-tree 2
(create-tree 4 empty-tree empty-tree)
(create-tree 5 empty-tree empty-tree))
(create-tree 3 empty-tree empty-tree))
--> ’(1 (2 4 5) 3)
Trong dng đn gin này, các nút ca cây A đc biu din bi giá tr ca nút ln lt
theo th t duyt cây gc

trái

phi.
V.4.1.3. Mt s ví d lp trình đn gin
1. m s lng các nút có trong mt cây
Xây dng hàm size có dng hàm nh sau :

; size : BinTree(T) → Integer
(define (size A)
(if (empty-tree? A)
0
(+ 1 (size (left A)) (size (right A)))))
; m s lng các nút có trong cây A  Hình V.10
````:
(size A)
--> 5
2. Tính đ cao ca mt cây
 cao ca mt cây khác rng là s cnh ti đa ni gc ca cây vi các lá ca nó.
Theo quy c, đ cao ca mt cây rng có th là −1. Xây dng hàm height có dng nh sau :
; height : BinTree(T) → Integer
(define (height A)
(if (empty-tree? A)
-1
(+ 1 (max (height (left A)) (height (right A))))))
;  cao ca cây A  Hình V.10.:
CU TRÚC D LIU 167
(height A)
--> 2
Kích thc n và đ cao h ca cây nh phân thng đc dùng đ đo đ phc tp tính toán.
Ta có :
[log
2
n] ≤ h ≤ n−1 (ký hiu [x] đ ch phn nguyên ca x)
Cây đy đ : n = 2
h+1
− 1
Cây suy thoái : n = h + 1

Có tt c
2
( 1)
n
n
C
n +
cây nh phân kích thc n,
vi là t hp chp p (cách chn p phn t) ca n (trong s n phn t).
n
p
C
C hai hàm size và height trên đu có chi phí tuyn tính theo n.
V.4.1.4. Duyt cây nh phân
Cây đc s dng ch yu đ t chc lu tr d liu nên khi cn khai thác ngi ta phi
duyt (traverse) hay thm (visit) ni dung ca cây. Có rt nhiu chin lc duyt cây. Do cây
đc xây dng đ quy, mt cách t nhiên, chng trình duyt cây có th s dng đ quy. Mi
chin lc duyt cây đu tr v kt qu là danh sách các nút đã đc duyt qua.
Ta s xét sau đây chin lc duyt cây nh phân theo chiu sâu. Trong trng hp tng
quát, thut toán có dng nh sau :
(define (run-around A)
(if (empty-tree? A)
<result>
(f (root A) ; duyt nút gc
(run-around (left A )) ; duyt cây con trái
(run-around (right A)))))) ; duyt cây con phi
Trong đó, <result> là kt qu tr v (thng là mt danh sách rng) và f là hàm x lý
mt phn t ca cây A (liên quan đn ni dung hay nhãn ca nút va đc duyt). Th t
duyt cây trên đây (gc-trái-phi) là th t trc hay còn đc gi là th t tin t (prefixe
order). Nu đi li th t duyt trên đây thì ta nhn đc :

− Th t gia hay trung t (infixe order) nu nút gc đc x lý gia các li gi đ quy
(trái-gc-phi).
− Th t sau hay hu t (postfixe order) nu nút gc đc x lý sau các li gi đ quy
(trái-phi-gc).
Tuy nhiên, ta cng có th có thêm ba th t duyt cây theo hng ngc li là : gc-phi-
trái, phi-gc-trái và phi-trái-gc.
Ví d hàm nodes sau đây duyt cây nh phân đ tr v danh sách tt c các nút ca cây
đó. Do hàm s dng lnh append nên có chi phí bc hai đi vi kích thc ca cây.
; nodes: BinTree(T) → List(T)
; Duyt cây nh phân theo th t trc :
(define (pre-nodes A)

168 LP TRÌNH HÀM
(if (empty-tree? A)
’()
(append (list (root A))
(pre-nodes (left A))
(pre-nodes (right A)))))
; Duyt cây nh phân theo th t gia :
(define (inf-nodes A)
(if (empty-tree? A)
’()
(append (inf-nodes (left A))
(list (root A))
(inf-nodes (right A)))))
; Duyt cây nh phân theo th t sau :
(define (post-nodes A)
(if (empty-tree? A)
’()
(append (post-nodes (left A))

(post-nodes (right A))
(list (root A)))))
 minh ho các thut toán duyt cây trên đây, gi s A là cây đc cho nh hình sau :

Hình V.11. Cây nh phân A có 7 nút
Cây A đc đnh ngha theo kiu đn gin :
(define A
(create-tree 1
(create-tree 2
(create-tree 4 empty-tree empty-tree)
(create-tree 5 empty-tree empty-tree))
(create-tree 3
(create-tree 6 empty-tree empty-tree)
(create-tree 7 empty-tree empty-tree)))
--> ’(1 (2 4 5) (3 6 7))
(pre-nodes A)
--> ’(1 2 4 5 3 6 7) ; th t trc
(inf-nodes A)
--> ’(4 2 5 1 6 3 7) ; th t gia
(post-nodes A)
--> ’(4 5 3 6 7 3 1) ; th t sau

1
2 3
4 5 6 7
CU TRÚC D LIU 169
V.4.2. Cu trúc cây tng quát
Trong trng hp cây tng quát, mi nút đu đc gn nhãn và có s lng cây con tu ý
không hn ch. Mt cách trc giác, cây có gn nhãn là mt cu trúc d liu gm mt giá tr
nhãn và mt danh sách có th rng các cây con gn nhãn. Nh vy cây không bao gi rng,

nhng danh sách các cây con có th rng. Trong trng hp cây ch có mt con, thì không có
khái nim cây con trái hay cây con phi. Ta cng cn chú ý rng cu trúc cây nh phân không
phi là trng hp riêng ca cây tng quát.
Ta có đnh ngha đ quy nh sau :
1. Mt danh sách các cây kiu T là mt rng (forest) kiu T.
2. Nu E là mt phn t kiu T và F là mt rng kiu T, thì cp (E, F) là mt cây kiu T.
3. Ch nhng đi tng đc đnh ngha  quy tc 1 là rng kiu T.
Ch nhng đi tng đc đnh ngha  quy tc 2 là cây kiu T.
Cho trc tp hp các cây kiu T. Vi mi cây T, các cây T
1
, ..., T
n
đc gi là con ca T.
Mt nhãn e nào đó là gc ca T. S con  mi nút là bc (degree) ca nút đó. Mt nút có có
bc 0 là mt lá
V.4.2.1. Kiu tru tng cây tng quát
Ta xây dng kiu tru tng ca cây tng quát gm Tree(T) và Forest(T)=List(Tree(T))
nh sau :
types Tree(T), Forest(T)
functions
() : Forest(T)
null? : Forest(T) → boolean
cons : Tree(T)×Forest(T) → Forest(T)
car : Forest(T) → Tree(T)
cdr : Forest(T) → Forest(T)
create-tree : T × Forest(T) → Tree(T)
root : Tree(T) → T
forest : Tree(T) → Forest(T)
leaf? : Tree(T) → boolean
var F: Forest(T); A, E: Tree(T)

preconditions
car(F) và cdr(F) ch xác đnh khi và ch khi F ≠ ()
axioms (các tiên đ liên quan đn list không nhc li  đây)
root(create-tree(E, F)) = E
forest(create-tree(E, F)) = F
leaf?(A) and forest(A)= ()
V.4.2.2. Biu din cây tng quát
Ta đa ra hai cách biu din cây tng quát nh sau :
1. Biu din cây nh mt b đôi
(define create-tree cons)
(define root car)

×