二叉查找树(C++)

news2024/7/6 17:32:52

背景:

  最近我要学习二叉平衡树了,在学习二叉平衡树之前,我需要学会二叉搜索树,因为二叉平衡树就是根据二叉搜索树的思想进行优化的。

二叉查找树简介:

  二叉查找树是什么呢?(也叫二叉搜索树)就是字面意思,首先是一颗二叉树:一棵树,最多分两个叉。

1.1 满二叉树

 上面那个图就是一颗二叉树,同时也是一颗满二叉树:每一个节点都有两个儿子,左儿子和右儿子。

  同时啊,还有完全二叉树:一棵深度为k的有n个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为i(1≤i≤n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树

1.2 完全二叉树

   然后最后一张图,就是普通的二叉树(就是只有每一个结点最多有两个儿子这一个特性):

1.3 普通二叉树

   然后,我们在看“搜索”,也就是说,我们建立起来的这颗二叉树,是用来搜索的,我们知道,搜索有很多种算法都可以,二分查找,枚举查找,启发式查找,为什么要用感觉很复杂的二叉树进行查找呢?那肯定是因为用二叉查找树来搜索比这些算法都快呀!

  接下来,给大家说一下二叉查找树的定义:一棵二叉树(满二叉树,完全二叉树,普通二叉树都可以),从根节点开始,根节点的左儿子一定小于根节点,根节点的右儿子肯定大于根节点,根节点的左儿子的左儿子也是一样,小于根节点的左儿子,根节点的左儿子的右儿子一定大于根节点的左儿子,以此类推,除了叶子节点,其他节点必须满足这个要求(等于的话算右儿子)

  也就是说,一棵二叉搜索树的所有左子节点肯定都小于根节点,所有的右子节点肯定都大于等于根节点,这样做有什么好处呢?首先,我们想要在二叉搜索树里查找一个数,首先比较第一个数与根节点的大小,如果一样,那就找到了,如果小于,那么前往左儿子那里继续重复这样的操作找,如果大于,那就前往右儿子那里继续重复这样的查找,直到找到为止。如果到了叶子节点,都没有找到这个数,那么在二叉搜索树里面就没有这个数。

  叶子结点就是一个没有左儿子和没有右儿子的结点(比方说上图1.3图中的4,8,9,6,10号结点就都是叶子结点)。

二叉查找树(二叉搜索树)

  以上这个图就是一棵二叉搜索树,每一个结点都符合左儿子小于结点,右儿子大于结点这一个条件,看上面这个数,是我们输入9个数之后建成的,如果用数组进行查找的话,最快需要1次,最久需要9次才可以找到,而这个二叉搜索树最快是1次,最慢只要3次就可以了,是按树的高度来计量的(而二分查找虽说有些时候是比二叉搜索树快,但是必须满足数组有序才可以,对于输入数据无序的情况来说,还是二叉搜索树好一些)。

  所以说,二叉搜索树搜索一个数的时间复杂度为O(logh)(h为二叉搜索树的高度,h最大为n,h最小为x,n为输入数的数量,2^x>=n).

程序(字典序):

  介绍:

    就是输入n个字符串,以字典序来进行创建二叉搜索树,字典序就是A~Z(保证输入字符串第一个字符为大写),首先比较第一个字符,比较二者的ASCII码,如果相等就比较下一个字符。

  结构体:

    首先,我们需要定义一个二叉树的结构Node,每一个结点里面存着一个字符串,可以定义一个char*类型的数组a,初始化为'\0'.

  然后是两个bool类型的变量lf和rf,初始化为false,代表着这个节点是否有左儿子,是否有右儿子,然后就是定义两个结构指针Node* lchidl和Node* rchild.分别表示左儿子和右儿子,都初始化为NULL(为空)。

struct Node{ //节点结构 
	char a[101]; //节点中的字符串 
	Node *lchild; //节点的左儿子 
	bool lf; //是否有左儿子 
	Node *rchild; //节点的右儿子 
	bool rf; //是否有右儿子 
	Node(){ //初始化 
		lchild=rchild=NULL; //左儿子和右儿子初始化为NULL(空) 
		lf=rf=false; //bool类型的rf和lf先初始化为false(假) 
	}
};

  初始化函数:

    因为我们在结构体里面定义的初始化函数只有根节才可以使用,初始化rchild和lchild的时候,我们需要用特定的一个初始化函数init来进行。

void init(Node* q){ //初始化函数init 
	q->lchild=q->rchild=NULL; //将左儿子和右儿子设置为NULL(空) 
	q->lf=q->rf=false; //将是否有左儿子、右儿子设置为false(假) 
	for(int i=0;i<101;i++) //进行初始化字符串 
	  q->a[i]='\0'; //将其设置为\0(空字符) 
}

   比较函数:

  我们需要写一个bool返回值的比较函数cmp,参数为两个字符串,是判断在字典序中,谁大谁小的。

bool cmp(char a[101],char b[101]){ //判断字符串谁大谁小 
	int t=strlen(a),t1=strlen(b); //求两个长度 
	if(t<t1){ //如果a长 
		for(int i=0;i<t;i++){ //比较 
			if(a[i]<b[i]) //ASCII码比较小的话 
			  return false; //返回false 
			if(a[i]>b[i]) //ASCII码比较大的话 
			  return true; //返回true 
		}
		return false; //如果执行完了,都没有返回,说明b是a的一个连续子串,返回false. 
	}
	else if(t1<t){
		for(int i=0;i<t1;i++){ //比较 
			if(a[i]<b[i]) //ASCII码比较小的话 
			  return false; //返回false 
			if(a[i]>b[i]) //ASCII码比较大的话 
			  return true; //返回true 
		}
		return false; //如果执行完了,都没有返回,说明a是b的一个连续子串,返回false.
	}
	for(int i=0;i<t1;i++){ //如果长度相同 
		if(a[i]<b[i]) //ASCII码比较小的话 
		  return false; //返回false 
		if(a[i]>b[i]) //ASCII码比较大的话 
		  return true; //返回true 
	}
}

  插入字符串

  接下来我们要写一个插入字符串的insert函数,参数为Node* q,char s[101],代表在q这个二叉查找树中插入s这个字符串。

  我们可以从根节点开始比较,如果小就去左儿子节点,如果大就去右儿子节点,如果到了一个节点,左子节点和右子节点都为NULL(空),那么就可以将s插入在这个位置。

bool insert(Node* q,char s[101]){ //插入s字符串到q上 
	while(1){ //一直执行 
		if(q->rchild==NULL&&q->lchild==NULL&&q->a[0]=='\0'){ //如果这个节点没有左子节点和右子节点,并且节点字符串为空 
			for(int i=0;i<strlen(s);i++) //进行插入操作 
			  q->a[i]=s[i]; //赋值 
			return true; //返回插入成功 
		}
		bool f=cmp(q->a,s); //比较当前节点字符串和s的大小 
		if(f){ //如果为真(小),去左子节点 
			if(!q->lf){ //如果这个节点没有左子节点 
				q->lchild=(Node*)malloc(sizeof(Node)); //分配空间 
				q->lf=true; //初始化 
				init(q->lchild); //初始化函数 
			}
			q=q->lchild; //前往 
		}
		else{ //如果为假(大) 
			if(!q->rf){ //如果这个节点没有右子节点
				q->rchild=(Node*)malloc(sizeof(Node));//分配空间 
				q->rf=true; //初始化 
				init(q->rchild); //初始化函数 
			}
			q=q->rchild; //前往 
		}
	}
	return false; //返回假 
}

判断函数

  函数pd的作用是比较两个字符串是否相等(在查找的时候需要用)!

bool pd(char a[101],char b[101]){ //判断a字符串和b字符串是否相等 
	if(strlen(a)!=strlen(b)) //如果长度都不一样 
	  return false; //肯定不一样 
	for(int i=0;i<strlen(a);i++) //每一个字符逐一对比 
	  if(a[i]!=b[i]) //如果有一个不一样的 
	    return false; //返回假 
	return true; //返回真 
}

查找函数

  我们还需要写一个查找函数searc,参数为Node* q,char s[101].是在q这个二叉搜索树中找s这个字符。

  我们可以应用递归的形式,如果q->lf为真,递归去q->lchild看看,如果q->rf为真,递归去q->rchild看看,每次到一个节点,都要比较这个结点和s是不是一样(pd函数),如果一样,返回为真。

void search(Node *q,char s[101]){ //在q里面查找s字符串 
	if(cz) //如果为真 
	  return ; //返回 
	sum++; //次数累计 
	if(pd(q->a,s)){ //如果相等 
		cz=true; //赋值为真 
		return ; //退出 
	}
	if(q->lf) //如果有左子节点 
	  if(cmp(q->a,s)) //比较 
	    search(q->lchild,s); //递归 
	if(q->rf) //如果有右子节点 
	  if(!cmp(q->a,s)) //比较 
	    search(q->rchild,s); //递归 
	return ; //返回 
}

输出二叉搜索树:

  我们可以写一个输出函数print,就是用来输出一颗二叉搜索树,也是和查找一样,运用递归的方式。

void print(Node* q){ //输出二叉搜索树 
	cout<<q->a<<endl; //输出这个节点的字符串 
	if(q->lchild==NULL&&q->rchild==NULL) //如果是叶子节点 
	  return ; //返回 
	if(q->lf) //如果有左子节点 
	  print(q->lchild); //递归 
	if(q->rf) //如果有右子节点 
	  print(q->rchild); //递归 
}

main主函数:

int main(){
	int n;
	cin>>n;
	char a[n][101];
	Node* p;
	p=(Node*)malloc(sizeof(Node));
	cin>>a[0];
	for(int i=0;i<strlen(a[0]);i++)
	  p->a[i]=a[0][i];
	for(int i=1;i<n;i++){
		cin>>a[i];
		bool f=insert(p,a[i]);
		if(!f){
			cout<<"插入失败!\n";
			return 0;
		}
	}
	cout<<endl; 
	print(p);
	char s[101];
	cout<<"输入要查找的字符串:\n";
	cin>>s;
	search(p,s);
	if(cz)
	  printf("查找成功,查找次数为%d!\n",sum);
	else
	  printf("二叉查找树中没有这个字符串!\n");
	return 0;
}

完整代码: 

#include<bits/stdc++.h>
using namespace std;
int sum=0;
bool cz;
struct Node{ //节点结构 
	char a[101]; //节点中的字符串 
	Node *lchild; //节点的左儿子 
	bool lf; //是否有左儿子 
	Node *rchild; //节点的右儿子 
	bool rf; //是否有右儿子 
	Node(){ //初始化 
		lchild=rchild=NULL; //左儿子和右儿子初始化为NULL(空) 
		lf=rf=false; //bool类型的rf和lf先初始化为false(假) 
	}
};
bool cmp(char a[101],char b[101]){ //判断字符串谁大谁小 
	int t=strlen(a),t1=strlen(b); //求两个长度 
	if(t<t1){ //如果a长 
		for(int i=0;i<t;i++){ //比较 
			if(a[i]<b[i]) //ASCII码比较小的话 
			  return false; //返回false 
			if(a[i]>b[i]) //ASCII码比较大的话 
			  return true; //返回true 
		}
		return false; //如果执行完了,都没有返回,说明b是a的一个连续子串,返回false. 
	}
	else if(t1<t){
		for(int i=0;i<t1;i++){ //比较 
			if(a[i]<b[i]) //ASCII码比较小的话 
			  return false; //返回false 
			if(a[i]>b[i]) //ASCII码比较大的话 
			  return true; //返回true 
		}
		return false; //如果执行完了,都没有返回,说明a是b的一个连续子串,返回false.
	}
	for(int i=0;i<t1;i++){ //如果长度相同 
		if(a[i]<b[i]) //ASCII码比较小的话 
		  return false; //返回false 
		if(a[i]>b[i]) //ASCII码比较大的话 
		  return true; //返回true 
	}
}
bool pd(char a[101],char b[101]){ //判断a字符串和b字符串是否相等 
	if(strlen(a)!=strlen(b)) //如果长度都不一样 
	  return false; //肯定不一样 
	for(int i=0;i<strlen(a);i++) //每一个字符逐一对比 
	  if(a[i]!=b[i]) //如果有一个不一样的 
	    return false; //返回假 
	return true; //返回真 
}
void init(Node* q){ //初始化函数init 
	q->lchild=q->rchild=NULL; //将左儿子和右儿子设置为NULL(空) 
	q->lf=q->rf=false; //将是否有左儿子、右儿子设置为false(假) 
	for(int i=0;i<101;i++) //进行初始化字符串 
	  q->a[i]='\0'; //将其设置为\0(空字符) 
}
bool insert(Node* q,char s[101]){ //插入s字符串到q上 
	while(1){ //一直执行 
		if(q->rchild==NULL&&q->lchild==NULL&&q->a[0]=='\0'){ //如果这个节点没有左子节点和右子节点,并且节点字符串为空 
			for(int i=0;i<strlen(s);i++) //进行插入操作 
			  q->a[i]=s[i]; //赋值 
			return true; //返回插入成功 
		}
		bool f=cmp(q->a,s); //比较当前节点字符串和s的大小 
		if(f){ //如果为真(小),去左子节点 
			if(!q->lf){ //如果这个节点没有左子节点 
				q->lchild=(Node*)malloc(sizeof(Node)); //分配空间 
				q->lf=true; //初始化 
				init(q->lchild); //初始化函数 
			}
			q=q->lchild; //前往 
		}
		else{ //如果为假(大) 
			if(!q->rf){ //如果这个节点没有右子节点
				q->rchild=(Node*)malloc(sizeof(Node));//分配空间 
				q->rf=true; //初始化 
				init(q->rchild); //初始化函数 
			}
			q=q->rchild; //前往 
		}
	}
	return false; //返回假 
}
void print(Node* q){ //输出二叉搜索树 
	cout<<q->a<<endl; //输出这个节点的字符串 
	if(q->lchild==NULL&&q->rchild==NULL) //如果是叶子节点 
	  return ; //返回 
	if(q->lf) //如果有左子节点 
	  print(q->lchild); //递归 
	if(q->rf) //如果有右子节点 
	  print(q->rchild); //递归 
}
void search(Node *q,char s[101]){ //在q里面查找s字符串 
	if(cz) //如果为真 
	  return ; //返回 
	sum++; //次数累计 
	if(pd(q->a,s)){ //如果相等 
		cz=true; //赋值为真 
		return ; //退出 
	}
	if(q->lf) //如果有左子节点 
	  if(cmp(q->a,s)) //比较 
	    search(q->lchild,s); //递归 
	if(q->rf) //如果有右子节点 
	  if(!cmp(q->a,s)) //比较 
	    search(q->rchild,s); //递归 
	return ; //返回 
}
int main(){
	int n;
	cin>>n;
	char a[n][101];
	Node* p;
	p=(Node*)malloc(sizeof(Node));
	cin>>a[0];
	for(int i=0;i<strlen(a[0]);i++)
	  p->a[i]=a[0][i];
	for(int i=1;i<n;i++){
		cin>>a[i];
		bool f=insert(p,a[i]);
		if(!f){
			cout<<"插入失败!\n";
			return 0;
		}
	}
	cout<<endl; 
	print(p);
	char s[101];
	cout<<"输入要查找的字符串:\n";
	cin>>s;
	search(p,s);
	if(cz)
	  printf("查找成功,查找次数为%d!\n",sum);
	else
	  printf("二叉查找树中没有这个字符串!\n");
	return 0;
}
/*
12
Jan
Feb
Mar
Apr
May
Jun
Jul
Aug
Sep
Oct
Nov
Dec
*/

 运行样例:

输入:

12
Jan
Feb
Mar
Apr
May
Jun
Jul
Aug
Sep
Oct
Nov
Dec

 输出:

Jan
Feb
Apr
Aug
Dec
Mar
Jun
Jul
May
Sep
Oct
Nov
输入要查找的字符串:
Oct
查找成功,查找次数为11!

总结;

  二叉搜索树是一个搜索速度很快的数据结构,但是他还有一种很严重的缺点,需要用二叉平衡树来进行优化。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/360920.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

国产无线蓝牙耳机哪个好?2023国产无线蓝牙耳机排行

随着蓝牙耳机的快速发展&#xff0c;近几年国产蓝牙耳机更是呈指数式爆发&#xff0c;越来越多的国产蓝牙耳机品牌被人们看到、认可。那么&#xff0c;国产无线蓝牙耳机哪个好&#xff1f;下面&#xff0c;我来给大家推荐几款国产蓝牙耳机&#xff0c;一起来看看吧。 一、南卡…

C语言实现动态管理通讯录信息系统(静态通讯录plus版)

文章目录前言&#xff1a;一.动态管理思想1.通讯录结构体声明发生变化2.通讯录结构体初始化发生变化3.通讯录能够动态增容4.通讯录销毁数据二.优化通讯录可持续读写信息1.保存通讯录中的信息到文件中2.加载文件信息到通讯录中三.源码1.text.c2.contact.c3.contact.h前言&#x…

Kotlin新手教程七(委托)

委托模式是软件设计模式中的一项基本技巧。在委托模式中&#xff0c;有两个对象参与处理同一个请求&#xff0c;接受请求的对象将请求委托给另一个对象来处理。kotlin中使用by实现委托。 一、类委托 类的委托实际就是一个类中定义的方法实际是调用另一个类中的对象的方法来实现…

5.7 BGP属-ORIGIN

配置BGP ORIGIN属性控制选路 1. 实验目的 熟悉BGP ORIGIN属性控制选路的应用场景掌握BGP ORIGIN属性控制选路的配置方法2. 实验拓扑 实验拓扑如图5-7所示: 图5-7:BGP ORIGIN属性控制选路 3. 实验步骤 (1)配置IP地址 …

实验室通风橱通风柜的构成

一、实验室通风橱通风柜简介通风柜是一个密闭的同时又能排风的工作空间。其设计目的是为了控制、稀释以及排除这个密闭空间内产生制造的烟气、气雾和微粒&#xff0c;同时它也是实验室预防泄露控制的重要组成部分。在大多数实验室中&#xff0c;通风柜是保护实验室操作者免受有…

ubantu python完整安装示例(python3.7.1演示)

文章目录前言准备源码包1.下载2.解压准备工作&#xff08;重要&#xff09;1.下载cmake(用于编译源码&#xff09;2.下载必要的Module注意事项编译安装链接并验证配置环境变量1.移除原3.5link2.更换默认python3 的版本为3.73.添加路径前言 为什么需要使用源码编译安装&#xf…

分享项目 - Vue3 + TS + element-ui-plus 项目 -- Table表格表单

文章目录前言项目地址以及怎么阅读别人的代码整体代码分页数据作者是怎么处理的 usePagination顺藤摸瓜找到 api 接口的封装api 接口再往底层找全局请求封装与请求拦截器 service.ts前言 今天看一个 ts 项目的 table 模块&#xff0c;亲身体验这是公司后台管理系统一定会使用到…

Springboot @Test 给Controller接口 写 单元测试

前言 最近有小伙伴问到怎么给 controller的接口写单元测试。 单元测试是开发必不可少的一个环节。 既然有人问到了&#xff0c;那我觉得可能不止一个人不会&#xff0c;那就按照惯例&#xff0c;出手。 正文 内容&#xff1a; 主要是get 和 post 两种请求方式的接口 的 单元测…

centos7 开机自启动自定义脚本

centos7 开机自启动自定义脚本背景配置自启动jar1.首先书写自启动脚本2.在rc.local中加入脚本reboot测试docker版本的自启动背景 项目中有遇到2个问题&#xff0c; 1&#xff1a; 使用java启动jar包 2&#xff1a; docker容器中自启动个服务。 这2个都要使用linux的开机自启动问…

AcWing 1017. 怪盗基德的滑翔翼

怪盗基德是一个充满传奇色彩的怪盗&#xff0c;专门以珠宝为目标的超级盗窃犯。而他最为突出的地方&#xff0c;就是他每次都能逃脱中村警部的重重围堵&#xff0c;而这也很大程度上是多亏了他随身携带的便于操作的滑翔翼。有一天&#xff0c;怪盗基德像往常一样偷走了一颗珍贵…

【图像分类】卷积神经网络之LeNet5网络模型实现MNIST手写数字识别

写在前面: 首先感谢兄弟们的关注和订阅,让我有创作的动力,在创作过程我会尽最大能力,保证作品的质量,如果有问题,可以私信我,让我们携手共进,共创辉煌。 在上一篇博文中我们对LeNet5网络模型的结构进行了剖析,本篇博文,我们将使用PyTorch搭建LeNet5实现MNIST手写数字…

使用vmware制作云平台redhat7.9镜像模板

一、概述 1.1 redhat7.9 定制镜像上传到云平台。 这个制作镜像得方式适用于多种iso 镜像。 将iso 镜像通过vmware 创建出一台虚机&#xff0c;对虚机做一些基础配置。在虚机上安装kvm 虚拟化得工具&#xff0c; 将iso 镜像在导入虚机种通过kvm创建一下虚机&#xff0c; 虚机创…

Embedding 理解

Word Embedding 单词表示最简单的是 one-hot 但是它的缺点是 矩阵表示过于稀疏&#xff0c;占用空间对相关的词语无法得知它们的含义是相近的。 Word Embedding 解决了上述两个缺点&#xff0c;一个 Word Embedding 直观的例子如下图所示。 每个维度表示一个特征&#xff0…

简述操作系统的文件系统

前言 文件系统是操作系统中负责管理持久数据的子系统&#xff0c;将用户的文件保存在硬盘等硬件设备中&#xff0c;即使断电了数据也不会丢失。 对于用户而言&#xff0c;文件是存储的最小单位&#xff0c;再少的数据也需要以文件的形式存储在外部存储器中。以硬盘为例&#…

金三银四,我不允许你们不知道这些软件测试面试题

01、您所熟悉的测试用例设计方法都有哪些&#xff1f;请分别以具体的例子来说明这些方法在测试用例设计工作中的应用。 答&#xff1a;有黑盒和白盒两种测试种类&#xff0c;黑盒有等价类划分法&#xff0c;边界分析法&#xff0c;因果图法和错误猜测法。白盒有逻辑覆盖法&…

DataX及DataX-Web

大数据Hadoop之——数据同步工具DataX数据采集工具-DataX datax详细介绍及使用 一、概述 DataX 是阿里云DataWorks数据集成的开源版本&#xff0c;在阿里巴巴集团内被广泛使用的离线数据同步工具/平台。DataX 实现了包括 MySQL、Oracle、OceanBase、SqlServer、Postgre、HDFS、…

前端将base64图片转换成file文件

1、base64转成file具体代码 // base64图片转file的方法&#xff08;base64图片, 设置生成file的文件名&#xff09;function base64ToFile(base64, fileName) {// 将base64按照 , 进行分割 将前缀 与后续内容分隔开let data base64.split(,);// 利用正则表达式 从前缀中获取图…

PAT 甲级 1002 python 测试点1未通过

题目&#xff1a; 思路&#xff1a; 1注意多项式非零项的数目 2两项相加为0时不输出 3 测试点1未通过 代码&#xff1a; C{} Alist(input().split()) Blist(input().split())count0 for i in range(int(A[0])):C[A[i*21]]float(A[i*22])count count 1for i in range(int(B…

支付宝支付功能使用

1、进入“蚂蚁金服开放平台” https://open.alipay.com/https://open.alipay.com/ 2、下载支付宝官方 demo&#xff0c;进行配置和测试 文档地址 手机网站支付 DEMO &#xff5c; 网页&移动应用支付宝文档中心https://opendocs.alipay.com/open/02no47 demo下载 网页…

如何使用ngxin的 upstream

1.引言&#xff1a; 1.1反向代理&#xff1a; 反向代理是充当Web服务器网关的代理服务器。当您将请求发送到使用反向代理的Web服务器时&#xff0c;他们将先转到反向代理&#xff0c;由该代理将确定是将其路由到Web服务器还是将其阻止。 这意味着有了反向代理&#xff0c;您…