用Scheme实现二分检索树(Binary Search Tree)

刚刚用Scheme做完这个小练习,看着写完的代码,连我自己都感到无比惊讶。程序非常短,定义数据结构、建立二叉树、中序遍历的代码加起来才30多行!下面我就来一步一步地讲讲我的思考过程。目标是从一个表中构建一棵二分检索树,对它进行中序遍历,将遍历序列保存在一个表中。为达到这个目标,首先搞清楚一个问题:二叉树是什么?嗯……是一种数据结构,由树根、左子树和右子树构成,其中左右子树也是二叉树。好,考虑到
摘要由CSDN通过智能技术生成

刚刚用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)

大功告成!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值