算法与数据机构入门经典-学习笔记3.2(二叉查找(排序)树-建立与查找、插入)

  • 本周的学习内容包括:

     1.树与二叉树

  a、树与二叉树的基本概念

  b、二叉树的前序、中序、后序遍历(递归与非递归)

    2.二叉查找树

   a、二叉查找树的基本概念

   b、二叉查找树的建立与查找

   f、二叉查找树节点插入

  c、二叉查找树节点删除

  d、使用二叉查找树进行排序

  e、例题:二叉查找树编码与解码

                                                                                            正文

今日学习:二叉查找树的基本概念、二叉查找树的建立与查找、二叉查找树节点插入、二叉查找树节点删除、使用二叉查找树进行排序、例题:二叉查找树编码与解码

 1、二叉查找(搜索\排序)树的基本概念

《算法与数据机构入门经典-学习笔记3.2(二叉查找(排序)树-建立与查找、插入)》

 《算法与数据机构入门经典-学习笔记3.2(二叉查找(排序)树-建立与查找、插入)》

《算法与数据机构入门经典-学习笔记3.2(二叉查找(排序)树-建立与查找、插入)》

2、二叉查找树的建立与查找

  • 建立

方法1:

《算法与数据机构入门经典-学习笔记3.2(二叉查找(排序)树-建立与查找、插入)》

导致在赋值节点时也不同:

 《算法与数据机构入门经典-学习笔记3.2(二叉查找(排序)树-建立与查找、插入)》

 方法2:

《算法与数据机构入门经典-学习笔记3.2(二叉查找(排序)树-建立与查找、插入)》

导致在赋值节点时也不同:

《算法与数据机构入门经典-学习笔记3.2(二叉查找(排序)树-建立与查找、插入)》

       体会下,两中的建立方法,其实就是在一个用了构造函数,所以就不需要初始化函数了,构造函数:是一种特殊的成员函数,主要是给对象分配空间,进行初始化,函数名和类名字相同。gou

  • 查找 

思路:二叉查找树查找数值的思路

《算法与数据机构入门经典-学习笔记3.2(二叉查找(排序)树-建立与查找、插入)》

 二叉树查找树查找数值,递归实现的代码:

《算法与数据机构入门经典-学习笔记3.2(二叉查找(排序)树-建立与查找、插入)》

  二叉树查找树查找数值,循环实现的代码:

《算法与数据机构入门经典-学习笔记3.2(二叉查找(排序)树-建立与查找、插入)》

认真对比一下:发现思路完全一样,实现时代码编写不同而已呀。。。 关于递归,就是一个大变小的过程,慢慢体会。

  • 二叉查找树查找数值的测试

用递归:

#include<stdio.h>
#include<malloc.h>
#include<string.h>

struct TreeNode //与二叉树的数据结构完全相同
{
	int val;//节点的值
	struct TreeNode *left;//指向左节点的指针
	struct TreeNode *right;//指向右节点的指针
	//TreeNode(int x):val(x),left(NULL),right(NULL){}//构造函数
};
//构造函数:是一种特殊的成员函数,主要是给对象分配空间,进行初始化,函数名和类名字相同
struct TreeNode TreeNodeInit(int val)
{
	struct TreeNode a;
	a.val = val;
	a.left = NULL;
	a.right = NULL;
	return a;
}

int BST_search(struct TreeNode* node, int value)
{
	if (node->val == value) { //当前节点值等于value,找到了返回真
		return 1;
	}

	if (value < node->val) {//value值少于当前node节点值
		if (node->left) {//node节点有左子树,继续搜索node节点左子树
			return BST_search(node->left, value);//递归实现,相当于将这个左子树当做node节点,进而去找它的左子树
		}
		else {
			return 0;
		}
	}
	else {//否则,value值比当前node节点值大
		if (node->right) {//右子树不为空,继续搜索node节点右子树
			return BST_search(node->right, value);//递归实现,相当于将这个左子树当做node节点,进而去找它的右子树
		}
		else {
			return 0;
		}
	}
}


int main()
{
	//struct TreeNode a = TreeNode(8);//节点8
	//struct TreeNode b = TreeNode(3);//节点3
	//struct TreeNode c = TreeNode(10);//节点10
	//struct TreeNode d = TreeNode(1);//节点1
	//struct TreeNode e = TreeNode(6);//节点6
	//struct TreeNode f = TreeNode(15);//节点15
	struct TreeNode a = TreeNodeInit(8);//节点8
	struct TreeNode b = TreeNodeInit(3);//节点3
	struct TreeNode c = TreeNodeInit(10);//节点10
	struct TreeNode d = TreeNodeInit(1);//节点1
	struct TreeNode e = TreeNodeInit(6);//节点6
	struct TreeNode f = TreeNodeInit(15);//节点15
	a.left = &b;//连接节点成树
	a.right = &c;//取地址运输符 表示c变量所占内存的地址
	b.left = &d;
	b.right = &e;
	c.right = &f;
	int i;
	for (i = 0; i < 20; i++) {
		if (BST_search(&a, i)) {
			printf("%d is in the BST.\n", i);
		}
		else {
			printf("%d is not in the BST.\n", i);
		}
	}
	return 0;
}

 

结果:

《算法与数据机构入门经典-学习笔记3.2(二叉查找(排序)树-建立与查找、插入)》

用循环:

#include<stdio.h>
#include<malloc.h>
#include<string.h>

struct TreeNode
{
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
};//定义树的节点

struct TreeNode TreeNodeInit(int val)
{
   //没有动态分配空间了啦 
	struct TreeNode a;
	a.val = val;
	a.left = NULL;
	a.right = NULL;
	return a;
}//树的节点的初始化

bool BST_search(struct TreeNode* node, int value)
{
	while (node) {//当节点不为空执行查找
		if (node->val == value) {
			return true; //返回存在
		}//先比较根节点(每次的左子树都可以看做是跟节点,再去做比较往左还是往右)
		if (value < node->val) {//往左
			node = node->left;
		}
		else {//往右
			node = node->right;
		}
	}
	return false;//返回不存在
}


int main()
{
	struct TreeNode a = TreeNodeInit(8);
	struct TreeNode b = TreeNodeInit(3);
	struct TreeNode c = TreeNodeInit(10);
	struct TreeNode d = TreeNodeInit(1);
	struct TreeNode e = TreeNodeInit(6);
	struct TreeNode f = TreeNodeInit(15);
	a.left = &b;
	a.right = &c;
	b.left = &d;
	b.right = &e;
	c.right = &f;
	int i;
	for (i = 0; i < 20; i++) {
		if (BST_search(&a, i)) {
			printf("%d is in the BST.\n", i);
		}
		else {
			printf("%d is not in the BST.\n", i);
		}
	}
	return 0;
}

 结果:

《算法与数据机构入门经典-学习笔记3.2(二叉查找(排序)树-建立与查找、插入)》

不知道,大家注意到了没有。关于节点初始化的时候,我准备了两份代码。

其一:

《算法与数据机构入门经典-学习笔记3.2(二叉查找(排序)树-建立与查找、插入)》

其二:就是有构造函数。

 《算法与数据机构入门经典-学习笔记3.2(二叉查找(排序)树-建立与查找、插入)》

悉心品味下两则区别。 c++中的构造函数时为了初始化的,是不用调用的,自己会执行,而且只执行一次。关于这个结构的构造函数,要学习一下,TerrNode(int x):val(x),left(NuLL),right(NULL){}.这么写哦。。。。

《算法与数据机构入门经典-学习笔记3.2(二叉查找(排序)树-建立与查找、插入)》

3、二叉查找树节点插入

  • 算法 思路

《算法与数据机构入门经典-学习笔记3.2(二叉查找(排序)树-建立与查找、插入)》

《算法与数据机构入门经典-学习笔记3.2(二叉查找(排序)树-建立与查找、插入)》

  •  代码(递归实现):
void BST_insert_digui(struct TreeNode *node, struct TreeNode *insert_node)
{
	if (insert_node->data < node->data)
	{
		if (node->left) //当左子树不空的时候,递归的将insert_node插入左子树
		{
			BST_insert_digui(node->left, insert_node);
		}
		else //当左子树为空的时,将node的左指针与带插入的节点相连接
		{
			node->left= insert_node;
		}
	}
	else
	{
		if (node->right)//当右子树不空的时候,递归的将insert_node插入右子树
		{
			BST_insert_digui(node->right, insert_node);
		}
		else //当右子树为空时,将node的右指针与待插入节点相连接
		{
			node->right = insert_node;
		}
	}
}
  • 代码(循环实现):

 《算法与数据机构入门经典-学习笔记3.2(二叉查找(排序)树-建立与查找、插入)》

测试代码:

样本一:用数组。用初始函数。

#include<stdio.h>
#include<malloc.h>
#include<string.h>

struct TreeNode
{
	int data;
	struct TreeNode* left;
	struct TreeNode* right;
};

struct TreeNode* TreeNodeInit(int val)
{
	struct TreeNode *a = (struct TreeNode*)malloc(sizeof(struct TreeNode));
	a->data = val;
	a->left = NULL;
	a->right = NULL;
	return a;
}

void BST_insert(struct TreeNode *node, struct TreeNode *insert_node)
{
	while (node != insert_node)
	{
		if (insert_node->data < node->data)
		{
			if (node->left == NULL)
			{
				node->left = insert_node;
			}
			node = node->left;
		}
		else
		{
			if (node->right == NULL)
			{
				node->right = insert_node;
			}
			node = node->right;
		}
	}
}

void BST_insert_digui(struct TreeNode *node, struct TreeNode *insert_node)
{
	if (insert_node->data < node->data)
	{
		if (node->left) //当左子树不空的时候,递归的将insert_node插入左子树
		{
			BST_insert_digui(node->left, insert_node);
		}
		else //当左子树为空的时,将node的左指针与带插入的节点相连接
		{
			node->left= insert_node;
		}
	}
	else
	{
		if (node->right)//当右子树不空的时候,递归的将insert_node插入右子树
		{
			BST_insert_digui(node->right, insert_node);
		}
		else //当右子树为空时,将node的右指针与待插入节点相连接
		{
			node->right = insert_node;
		}
	}
}

//递归实现前序遍历
void preorder_print(struct TreeNode *node, int layer) {
	if (!node) {
		return;
	}
	int i;
	for (i = 0; i < layer; i++) {
		printf("-----");
	}
	printf("[%d]\n", node->data);
	preorder_print(node->left, layer + 1);
	preorder_print(node->right, layer + 1);
}

int main()
{
	struct TreeNode *root = TreeNodeInit(8);
	int test[] = { 3, 10, 1, 6, 15 };
	int i;
	for (i = 0; i < 5; i++) {
		BST_insert(root, TreeNodeInit(test[i]));//学会用函数呀
	}
	preorder_print(root, 0);
	return 0;
}

 样本二:用容器。用构造函数。

#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include <vector>
struct TreeNode
{
	int data;
	struct TreeNode* left;
	struct TreeNode* right;
	TreeNode(int x) :data(x), left(NULL), right(NULL) {}//构造函数
};



void BST_insert(struct TreeNode *node, struct TreeNode *insert_node)
{
	while (node != insert_node)
	{
		if (insert_node->data < node->data)
		{
			if (node->left == NULL)
			{
				node->left = insert_node;
			}
			node = node->left;
		}
		else
		{
			if (node->right == NULL)
			{
				node->right = insert_node;
			}
			node = node->right;
		}
	}
}

void BST_insert_digui(struct TreeNode *node, struct TreeNode *insert_node)
{
	if (insert_node->data < node->data)
	{
		if (node->left) //当左子树不空的时候,递归的将insert_node插入左子树
		{
			BST_insert_digui(node->left, insert_node);
		}
		else //当左子树为空的时,将node的左指针与带插入的节点相连接
		{
			node->left= insert_node;
		}
	}
	else
	{
		if (node->right)//当右子树不空的时候,递归的将insert_node插入右子树
		{
			BST_insert_digui(node->right, insert_node);
		}
		else //当右子树为空时,将node的右指针与待插入节点相连接
		{
			node->right = insert_node;
		}
	}
}

void preorder_print(struct TreeNode *node, int layer) {
	if (!node) {
		return;
	}
	int i;
	for (i = 0; i < layer; i++) {
		printf("-----");
	}
	printf("[%d]\n", node->data);
	preorder_print(node->left, layer + 1);
	preorder_print(node->right, layer + 1);
}

int main()
{
	struct TreeNode root = TreeNode(8);
	std::vector<struct TreeNode > node_vec;
	int tset[] = { 3,10,1,6,15 };
	for (int i=0; i < 5; i++)
	{
		node_vec.push_back(TreeNode(tset[i]));
	}
	for (int i = 0; i < node_vec.size(); i++)
	{
		BST_insert(&root, &node_vec[i]);
	}
	preorder_print(&root, 0);
	return 0;
}

     这里还要注意一下问题:关于容器的用法,以及如何改代码。改错误。多写,编译器会告诉你错误在哪的。下面告诉你一下容器怎么用,会用容器也方便很多,学会,所以在后面插播了一些容器的内容。

 《算法与数据机构入门经典-学习笔记3.2(二叉查找(排序)树-建立与查找、插入)》

《算法与数据机构入门经典-学习笔记3.2(二叉查找(排序)树-建立与查找、插入)》

                                                                                        插个小广告

       我发现实现排序可应用很多方法:除了已经学习过的,如:堆排序,快速排序,归并排序,冒泡排序等等。到现在俩看看今天学习的内容吧,其实还可以用二叉查找树(进行中序遍历结果就是升序) 。思路是什么呢?

  1.       输入数据(用数组存)
  2.       将数组中的数值一一初始化为节点(二叉树结构)
  3.       已知root,二叉查找树节点插入算法
  4.       对建立好的二叉查找树进行中序遍历,输出即可得到升序。

代码如下:

#include<stdio.h>
#include<malloc.h>
#include<string.h>

#include <iostream>

using namespace std;

struct TreeNode //与二叉树的数据结构完全相同
{
	int val;//节点的值
	struct TreeNode *left;//指向左节点的指针
	struct TreeNode *right;//指向右节点的指针
};
struct TreeNode* TreeNodeInit(int val)
{

	struct TreeNode* node = (struct TreeNode*)malloc(sizeof(struct TreeNode));//动态分配空间
	node->val = val;
	node->left = NULL;
	node->right = NULL;
	return node;
}

//查找
int BST_search(struct TreeNode* node, int value)
{
	if (node->val == value) { //当前节点值等于value,找到了返回真
		return 1;
	}

	if (value < node->val) {//value值少于当前node节点值
		if (node->left) {//node节点有左子树,继续搜索node节点左子树
			return BST_search(node->left, value);//递归实现,相当于将这个左子树当做node节点,进而去找它的左子树
		}
		else {
			return 0;
		}
	}
	else {//否则,value值比当前node节点值大
		if (node->right) {//右子树不为空,继续搜索node节点右子树
			return BST_search(node->right, value);//递归实现,相当于将这个左子树当做node节点,进而去找它的右子树
		}
		else {
			return 0;
		}
	}
}
//插入
void BST_insert(TreeNode *node,TreeNode *insert_node) 
{
	while (node != insert_node)// 当node 是 insert_node时那么就可以退出循环了,此时表示现在的node(根节点)是插入到的insert_node了。
	{
		if (insert_node->val < node->val)
		{
			if (!node->left) 
			{
				node->left = insert_node;
			}
			node = node->left;//如果左子树不为空则调转下一个左子树
		}
		else
		{
			if (!node->right) 
			{
				node->right = insert_node;
			}
			node = node->right;
		}
	}
}
//中序遍历
void middle_order(struct TreeNode* node)
{
	if (node == NULL)
	{
		return;
	}

	middle_order(node->left);//左节点
	printf("%d ", node->val);//父
	middle_order(node->right);//右节点
}//中序遍历
int main()
{

	int len;
	int *arry;
	cout << "请输入开辟动态数组的长度:" << endl;
	cin >> len;
	//长度乘以int的正常大小,才是动态开辟的大小 
	arry = (int*)malloc(len * sizeof(int));
	cout << "请逐个输入动态数组成员:" << endl;
	for (int i = 0; i<len; ++i)
	{
		//此处不可以写成:cin>>*p[i] 
		cin >> arry[i];
	}
	TreeNode *root= TreeNodeInit(arry[0]);
	//二叉树查找树插入
	for (int i = 1; i<len; ++i)
	{
		BST_insert(root, TreeNodeInit(arry[i]));
	}
	cout << "升序排序后:" << endl;
	middle_order(root);
	return 0;
}

 结果:

《算法与数据机构入门经典-学习笔记3.2(二叉查找(排序)树-建立与查找、插入)》

                                                                   温习一下内容:命名空间、头文件命名规则、容器

命名空间 

     由于大型软件都是由多个模块组成,而且是分不同的人完成,然后组成一个完整的程序。假如不同的人分别定义函数和类,放在不同的头文件,在主文件需要用到这些函数和类时,就用#include 命令行将这些头文件包括进来。由于不同人设计的头文件可能用了相同的名字来定义函数或类,这样,在程序中就会出现命名冲突,进而程序出错。且,如果用第三方的库,也会产生同样的问题。为解决这个问题,C++引入了命名空间,用来处理程序中国常见的同名冲突问题。其作用就类似于操作系统中的目录和文件的关系。把文件分别放在不同的子目录中,不同子目录中的文件可以同名,调用文件时需要指出文件路径。 

  • 声明命名空间方法:
namespace NS
{
	int i = 5;
	int  j = 4;
}

花括号:命名空间的作用域。namespce:定义命名空间所必须写的关键字。NS:命名空间的名字。

  • 命名空间使用方法:
  1. 在文件中使用: using namespace std;std是标准C++指定的一个命名空间,标准C++库中的所有标识符都是在这个名为std的命名空间中定义的,或者说标准头文件(如iostream)中的函数、类、对象、类模板是在命名空间std中定义的。
  2. 在该标识符前面加上命名空间及作用域符号“::”,如:std::cout<<“1″<<endl;

头文件命名规则

ANSI C++建议头文件不带拓展名.h,C++中都不带拓展名.h,如:iostream,cmath等,但是为了使得原来编写的C++也能运行,在C++中可以使用带.h的头文件和不带.h的头文件。
 

#inclued <cstdio> // 相当于c程序中的#include <stdio.h>

#include <cstring>//相当于c程序中的#include <string.h>

using namespace std //声明使用命名空间std

使用头文件的方法是等价的,可以任意选用,但使用带拓展名的头文件时,不需要在命名空间中声明命名空间std。

 容器

其实容器与数组类似,包含一组地址连续的存储单元,对容器有可以进行查询、插入、删除的常见操作。

  • 容器使用:需要头文件 #include <vector>
  • 容器定义:
std::vector<数据类型>;// 容器名字;如:std::vector<int> number;
  • 插入函数inset
number.insert(number.begin, 99);//在头插入99
number.insert(number.end, 100);//在尾插入100
number.insert(number.begin, 4, 4);//在头部插入4个4到容器

  • 删除函数earse
number.erase(number.begin());//删除头
  • back()和pop()
number.push_back(99);//添加一个元素到vector末尾
number.pop_back()//删除当前最好的一个元素
  • vector中还提供了at函数负责返回指定位置的元素。与数组运算符[]相比,at()函数更加安全,不会存在访问vextor内越界的元素。
number.at(i)
  • vertor 的构造函数
vector<int> number(5,99);//num=5,val=99,构造一个由参数num表示个数,参数val表示值的vector
vector<int> number1(number);//构造一个与number相同的vectore
vecter<int> number2(number.begin(),number.end());//构造一个值取自地带器的vector,开始位置和终止位置由参数指定。
  • vector的访问信息
  1. max_size()返回vector可以容纳元素的数量。
  2. size()返回vector当前元素的数量。
  3. capacity()返回所能容纳的元素数量(在不重新分配内存的情况下)
  4. empty()判断是非空,为空返回ture,否则flase。
  • 存取vector元素

存储vector信息都可以使用构造函数、push_back()、insert()、数组运算符、赋值运算符、pop_back()、erase()、begin()、end()、rbegin()、size()、maxsize等、 

  •  关于运算符

==、!=、<=、>=、<、>、vector之前大小的比较是按照词典规则,要访问vector中的某特定位置的元素可以使用[]操作。 

如果两个vector具有相同的容量。且所有相同位置的元素都相同,则这两个vector被认为是相等的。如果vector用来存储用户自定义类的对象,必须重载“==”和“<” 运算符。

    原文作者:二叉查找树
    原文地址: https://blog.csdn.net/qq_37791134/article/details/81218044
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞