算法导论::第三部分::第12章:文中程序和课后练习

本文详细讲解了二叉搜索树的基本概念和性质,包括如何利用比较排序建立最小高度的二叉搜索树,并分析了不同操作的时间复杂度。在练习中,通过实例深入探讨了二叉搜索树的查询、后继结点、前驱结点等特性,以及相关错误案例分析。
摘要由CSDN通过智能技术生成

第一部分:基础知识

12 二叉搜索树

12.1 二叉搜索树

  利用比较排序,建立的二叉搜索树,树的根是元素的中值(偶数时取小者),使得树的高度取最小。程序如下:

//#include"buildBinarySearchTree.h"
#include<iostream>
#include<vector>
#include<algorithm>
#include<cmath>

using namespace std;

int Left(int i){
	return 2*i+1;
}
int Right(int i){
	return 2*i+2;
}
int Parent(int i){
	return (i-1)/2;
}
void buildBinarySearchTree
(vector<int>& input,vector<int>& output,int left,int right,int pos){
	if (left==right){
		int middle=(left+right)/2;
		output[pos]=input[middle];
		return;
	}else{
		int middle=(left+right)/2;
		output[pos]=input[middle];
		if (left<=middle-1)
			buildBinarySearchTree(input,output,left,
				middle-1,Left(pos));
		if (right>=middle+1)
			buildBinarySearchTree(input,output,
				middle+1,right,Right(pos));
		return;
	}
}
vector<int> BuildTree(vector<int>& input){
	int highth=log(input.size())/log(2)-1;
	int size=pow(2,highth+2)-1;
	vector<int> output(size,-9999);
	sort(input.begin(),input.end());
	buildBinarySearchTree(input,output,0,input.size()-1,0);
	return output;
}
void InorderTreeWalk(vector<int>& A,int x=0){
	if (x<A.size()&&A[x]!=-9999){
		InorderTreeWalk(A,Left(x));
		cout<<A[x]<<" ";
		InorderTreeWalk(A,Right(x));
	}
}
#include"buildBinarySearchTree.h"

int main(int argc,char** argv){
	vector<int> A={2,1,3,4,123,123,5,7,98,123123};
	auto B=BuildTree(A);
	InorderTreeWalk(B);
}
练习12.1-1

  高度为2时:

1
4
5
10
17
16
21

  其他同理可以得到。

练习 12.1-2

  二叉搜索树的父节点的关键字是其子树的中间的值(或者等于),而最小堆的父节点的关键字比其子树的关键字都要小(或者等于)。最小堆是不能在 O ( n ) O(n) O(n)输出一棵 n n n个结点树的关键字的。这是因为,如果每次取父节点的值达到从小到大输出的话,每次输出必须要维护一次最小堆的性质,所以其时间复杂度为 Θ ( n l g n ) \Theta(nlgn) Θ(nlgn)

练习12.1-3
#include"buildBinarySearchTree.h"

void InorderTreeWalk2(vector<int>& A,int AvalidNumber){
	vector<int> print;
	int x=0,cnt=0;
	while(cnt<AvalidNumber){
		if(x<A.size()&&A[x]!=-9999){
			print.push_back(x);
			x=Left(x);
		}else{
			cout<<A[print[print.size()-1]]<<" ";
			cnt++;
			print.pop_back();
			x=Right(Parent(x));
			if(cnt>=AvalidNumber)
				break;
			if(x<A.size()&&A[x]!=-9999){
				print.push_back(x);
				x=Left(x);
			}else{
				x=print[print.size()-1];
				cout<<A[print[print.size()-1]]<<" ";
				cnt++;
				print.pop_back();
				x=Right(x);
			}
		}
	}
	cout<<endl;
}

int main(int argc,char** argv){
	vector<int> A={2,1,3,4,123,5,7,98,123123};
	auto Asize=A.size();
	auto B=BuildTree(A);
	InorderTreeWalk2(B,Asize);
}
练习12.1-4
#include"buildBinarySearchTree.h"

void PreorderTreeWalk(vector<int>& A,int x=0){
	if (x<A.size()&&A[x]!=-9999){
		cout<<A[x]<<" ";
		PreorderTreeWalk(A,Left(x));
		PreorderTreeWalk(A,Right(x));
	}
}
void PostorderTreeWalk(vector<int>& A,int x=0){
	if (x<A.size()&&A[x]!=-9999){
		PostorderTreeWalk(A,Left(x));
		PostorderTreeWalk(A,Right(x));
		cout<<A[x]<<" ";
	}
}
int main(int argc,char** argv){
	vector<int> A={2,1,3,4,123,5,7,98,123123};
	auto B=BuildTree(A);
	PreorderTreeWalk(B);
	cout<<endl;
	PostorderTreeWalk(B);
	cout<<endl;
}
练习12.1-5

  前面的建立树的过程就能知道,排序数组需要 Θ ( n l g n ) \Theta(nlgn) Θ(nlgn),建立二叉搜索树只需要 Θ ( n ) \Theta(n) Θ(n),所以最坏情况下的时间复杂度为 Θ ( n l g n ) \Theta(nlgn) Θ(nlgn)

12.2 查询二叉搜索树

  二叉树的 s e a r c h ( ) search() search() m a x i m u m ( ) maximum() maximum() m i n i m u m ( ) minimum() minimum() s u c c e s s o r ( ) successor() successor() p r e d e c e s s o r ( ) predecessor() predecessor(),函数如下:

#include"buildBinarySearchTree.h"

int TreeSearch(vector<int>A, int k,int x=0){
	while(x!=-9999&&x<A.size()&&k!=A[x]){
		if(k<A[x])
			x=Left(x);
		else 
			x=Right(x);
	}
	return x;
}
int TreeMinimum(vector<int> A,int x=0){
	while(x<A.size()&&A[x]!=-9999)
		x=Left(x);
	return Parent(x);
}
int TreeMaximum(vector<int> A,int x=0){
	while(x<A.size()&&A[x]!=-9999)
		x=Right(x);
	return Parent(x);
}
int TreeSuccssor(vector<int>A,int x){
	if (Right(x)<A.size()&&Right(x)!=-9999)
		return TreeMinimum(A,Right(x));
	int y=Parent(x);
	while(y<A.size()&&y!=-9999&&x==Right(y)){
		x=y;
		y=Parent(y);
	}
	return y;
}
int TreePredecessor(vector<int>A,int x){
	if (Left(x)<A.size()&&Left(x)!=-9999)
		return TreeMaximum(A,Left(x));
	int y=Parent(x);
	while(y<A.size()&&y!=-9999&&x==Left(y)){
		x=y;
		y=Parent(y);
	}
	return y;
}

int main(int argc,char** argv){
	vector<int> A={2,1,3,4,123,123,5,7,98,123123};
	auto Asize=A.size();
	auto B=BuildTree(A);
	InorderTreeWalk(B);
	cout<<endl;
	cout<<B[TreeSearch(B,5)]<<endl;
	cout<<B[TreeMinimum(B)]<<endl;
	cout<<B[TreeMaximum(B)]<<endl;
	cout<<B[TreeSuccssor(B,TreeSearch(B,5))]<<endl;
	cout<<B[TreePredecessor(B,TreeSearch(B,5))]<<endl;
}
练习12.2-1

   c c c d d d是错的。
  在 c c c中: 240 240 240 911 911 911的左孩, 912 912 912 240 240 240的右孩,而 912 &gt; 911 912&gt;911 912>911确在 911 911 911的左孩边,违反二叉搜索树性质;
  在 d d d中:类似上面的原因, 299 &lt; 347 299&lt;347 299<347确在 347 347 347的右孩边。

练习12.2-2

  简单,略。

练习12.2-3

  见前面,略。

练习12.2-4

  简单,略。比如: 1 , 4 , 3 , 2 , 2.1 1,4,3,2,2.1 1,4,3,2,2.1。查找路径为 1 , 4 , 3 , 2 1,4,3,2 1,4,3,2。显然 2.1 2.1 2.1属于 C C C 4 4 4属于 B B B 2.1 &lt; 4 2.1&lt;4 2.1<4

练习12.2-5

  反证就出来了,如果一个结点 a a a的后继 b b b有左孩 c c c,显然, a &lt; = c &lt; = b a&lt;=c&lt;=b a<=c<=b这与 b b b a a a的后继相矛盾。所以结点的后继没有左孩。前驱没右孩同理。

练习12.2-6

  准确地说,如果一个结点 x x x的子树无右孩,它的后继是第一个视 x x x为左子树中结点的先祖(不大理解书中"最低层先祖"是否指这个,还是指根结点)。
  证明很明显,如果 x x x无右孩,则它是一棵视它为右子树的树的最大值,那么这颗树的根结点 y y y的双亲 y . p y.p y.p和双亲的右树结点大于 x x x,而由搜索二叉树的性质知道,结点 y . p y.p y.p的值是比其右树的所有结点值要小,所以 x x x的后继是 y . p y.p y.p,而且由上面推述可知, y = y . p . l e f t y=y.p.left y=y.p.left也是 x x x的祖先。

练习12.2-7

  显然,在一棵以 x 0 x_0 x0为根结点的搜索二叉树中,其往下搜索(指 x = x . l e f t x=x.left x=x.left或者 x = x . r i g h t x=x.right x=x.right)的次数 T d o w n T_{down} Tdown是大于往上搜索(指 x = x . p x=x.p x=x.p)的次数 T u p T_{up} Tup的。所以此算法的时间复杂度 T T T有:
T d o w n ≤ T ≤ 2 T d o w n T_{down}\leq T\leq 2T_{down} TdownT2Tdown
  又由于往下搜索历遍所有元素所以 T d o w n = n − 1 T_{down}=n-1 Tdown=n1,所以时间复杂度 T = Θ ( n ) T=\Theta (n) T=Θ(n)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值