用Scheme實現二分檢索樹(Binary Search Tree)

來源:互聯網
上載者:User

剛剛用Scheme做完這個小練習,看著寫完的代碼,連我自己都感到無比驚訝。程式非常短,定義資料結構、建立二叉樹、中序遍曆的代碼加起來才30多行!下面我就來一步一步地講講我的思考過程。

目標是從一個表中構建一棵二分檢索樹,對它進行中序遍曆,將遍曆序列儲存在一個表中。為達到這個目標,首先搞清楚一個問題:二叉樹是什嗎?嗯……是一種資料結構,由樹根、左子樹和右子樹構成,其中左右子樹也是二叉樹。好,考慮到Scheme語言裡唯一的資料結構是表(List),不妨把二叉樹定義為:
(list root left-branch right-branch)

接下來就可以實現一個函數make-tree,它把二叉樹的三個部分組合成一棵二叉樹:
(define (make-tree root left-branch right-branch)
  (list root left-branch right-branch))

其實make-tree就是list的同義語,為什麼要這樣做呢?一是為了閱讀方便;另一個原因是這樣做起到了一種“封裝”的作用,如果日後資料結構發生改動,不至於引發劇烈的“代碼地震”。

樹的結構定義好了,自然地,下一步應該是想怎麼從這樣一種資料結構裡分離裡每一部分。我實現了三個函數,root、left-branch、right-branch,都跟一個參數,表示一棵樹,分別返回樹根、左子樹和右子樹。由上面的定義,樹根是表中的第一個元素,所以有
(define (root tree)
  (car tree))
左子樹位居第二,即(car (cdr tree)),由於car、cdr在Scheme中用得非常頻繁,所以相鄰的可以合并在一起寫成cadr。因此
(define (left-branch tree)
  (cadr tree))
類似地,可以寫出right-branch:
(define (right-branch tree)
  (caddr tree))

還有一個問題,就是空樹如何表示。我規定用一個空表“()”表示空樹。這樣就可以寫出一個判斷一棵樹是否為空白的函數:
(define (empty-tree? tree)
  (null? tree))

好了,以上這些就是我們的基本工具。非常簡陋是不是?一會兒你也會驚訝的。

既然要構建二叉樹,就先考慮最簡單的情況:空樹和只有一個結點的樹。空樹好說,就是'();只有一個結點的樹我把它定義為左右子樹均為空白的二叉樹。我用make-node來實現,它以一個數為參數,結果就是以那個數為根的左右子樹為空白的二叉樹:
(define (make-node num)
  (make-tree num '() '()))

看起來非常直觀。接下來應該考慮如何構建二分檢索樹了。我是這樣定義的:二分檢索樹是對於樹中的任何非空結點,它的左子結點的值小於它的值或者為空白,右子結點的值大於等於它的值或者為空白。二分檢索樹可以用一個一個結點插入的方法來構建。不難得到這樣的一個遞迴演算法:

  • 如果要插入的樹為空白,結果就是這個結點,否則:

    • 如果要插入的結點的值小於樹根結點的值,則插入左子樹;
    • 如果要插入的結點的值大於等於樹根結點的值,則插入右子樹。

代碼如下,其中(insert-num tree num)的含義是把一個數num插入到二分檢索樹tree中,結果仍為二分檢索樹:
(define (insert-num tree num)
  (if (empty-tree? tree)
      (make-node num)
      (cond ((< num (root tree))
             (make-tree (root tree)
                        (insert-num (left-branch tree) num)
                        (right-branch tree)))
            ((>= num (root tree))
             (make-tree (root tree)
                        (left-branch tree)
                        (insert-num (right-branch tree) num))))))

再來看看目標:從一個表中構建一棵二分檢索樹。現在能一個一個地構建二叉樹了,怎麼才能一下子處理一堆呢?我希望構建樹的函數用起來像這樣:(build-tree (list ....))。嗯……稍微一想就出來了:如果表為空白,結果就是一棵空樹,否則,假設表中有n個元素,後n-1個元素已經構成了二分檢索樹,要做的就是把第一個元素插入到樹中。這不又是一個遞迴演算法嗎?然後我聽到鍵盤一陣輕輕的響聲:
(define (build-tree items)
  (if (null? items)
      '()
      (insert-num (build-tree (cdr items)) (car items))))

細心一點你會發現,這個方法是從後往前倒著插入到二叉樹中的。好了,我們可以試試了:
(define t1 (build-tree (list 1 2 4 -1)))
(define t2 (build-tree (list 1 3 9 21)))
t1
t2
結果為:
(-1 () (4 (2 (1 () ()) ()) ()))
(21 (9 (3 (1 () ()) ()) ()) ())

看上去不錯,你還可以再試幾個,應該是不會出問題的。再看看離目標有多玩……哈!就差一個了:對它進行中序遍曆,將遍曆序列儲存在一個表中。中序遍曆的方法大家都會。不過這裡是要把結果儲存在一個表中。這也不難。Scheme中對錶有一個操作append,它可以把任意多的表合并成一個表,並且表的順序不變。這樣就可以用append來產生這個表:“遍曆左子樹所得的表+根結點+遍曆右子樹所得的表”就是最終目標!

(define (mid-order-traverse tree)
  (if (empty-tree? tree)
      '()
      (append (mid-order-traverse (left-branch tree))
              (list (root tree))
              (mid-order-traverse (right-branch tree)))))

試試吧:
(mid-order-traverse t1)
(mid-order-traverse t2)
結果是:
(-1 1 2 4)
(1 3 9 21)

大功告成!

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.