- 本周的学习内容包括:
1.树与二叉树
a、树与二叉树的基本概念
b、二叉树的前序、中序、后序遍历(递归与非递归)
2.二叉查找树
a、二叉查找树的基本概念
b、二叉查找树的建立与查找
f、二叉查找树节点插入
c、二叉查找树节点删除
d、使用二叉查找树进行排序
e、例题:二叉查找树编码与解码
正文
今日学习:二叉查找树的基本概念、二叉查找树的建立与查找、二叉查找树节点插入、二叉查找树节点删除、使用二叉查找树进行排序、例题:二叉查找树编码与解码
1、二叉查找(搜索\排序)树的基本概念
2、二叉查找树的建立与查找
- 建立
方法1:
导致在赋值节点时也不同:
方法2:
导致在赋值节点时也不同:
体会下,两中的建立方法,其实就是在一个用了构造函数,所以就不需要初始化函数了,构造函数:是一种特殊的成员函数,主要是给对象分配空间,进行初始化,函数名和类名字相同。gou
- 查找
思路:二叉查找树查找数值的思路
二叉树查找树查找数值,递归实现的代码:
二叉树查找树查找数值,循环实现的代码:
认真对比一下:发现思路完全一样,实现时代码编写不同而已呀。。。 关于递归,就是一个大变小的过程,慢慢体会。
- 二叉查找树查找数值的测试
用递归:
#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;
}
结果:
用循环:
#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;
}
结果:
不知道,大家注意到了没有。关于节点初始化的时候,我准备了两份代码。
其一:
其二:就是有构造函数。
悉心品味下两则区别。 c++中的构造函数时为了初始化的,是不用调用的,自己会执行,而且只执行一次。关于这个结构的构造函数,要学习一下,TerrNode(int x):val(x),left(NuLL),right(NULL){}.这么写哦。。。。
3、二叉查找树节点插入
- 算法 思路
- 代码(递归实现):
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;
}
}
}
- 代码(循环实现):
测试代码:
样本一:用数组。用初始函数。
#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;
}
这里还要注意一下问题:关于容器的用法,以及如何改代码。改错误。多写,编译器会告诉你错误在哪的。下面告诉你一下容器怎么用,会用容器也方便很多,学会,所以在后面插播了一些容器的内容。
插个小广告
我发现实现排序可应用很多方法:除了已经学习过的,如:堆排序,快速排序,归并排序,冒泡排序等等。到现在俩看看今天学习的内容吧,其实还可以用二叉查找树(进行中序遍历结果就是升序) 。思路是什么呢?
- 输入数据(用数组存)
- 将数组中的数值一一初始化为节点(二叉树结构)
- 已知root,二叉查找树节点插入算法
- 对建立好的二叉查找树进行中序遍历,输出即可得到升序。
代码如下:
#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;
}
结果:
温习一下内容:命名空间、头文件命名规则、容器
命名空间
由于大型软件都是由多个模块组成,而且是分不同的人完成,然后组成一个完整的程序。假如不同的人分别定义函数和类,放在不同的头文件,在主文件需要用到这些函数和类时,就用#include 命令行将这些头文件包括进来。由于不同人设计的头文件可能用了相同的名字来定义函数或类,这样,在程序中就会出现命名冲突,进而程序出错。且,如果用第三方的库,也会产生同样的问题。为解决这个问题,C++引入了命名空间,用来处理程序中国常见的同名冲突问题。其作用就类似于操作系统中的目录和文件的关系。把文件分别放在不同的子目录中,不同子目录中的文件可以同名,调用文件时需要指出文件路径。
- 声明命名空间方法:
namespace NS
{
int i = 5;
int j = 4;
}
花括号:命名空间的作用域。namespce:定义命名空间所必须写的关键字。NS:命名空间的名字。
- 命名空间使用方法:
- 在文件中使用: using namespace std;std是标准C++指定的一个命名空间,标准C++库中的所有标识符都是在这个名为std的命名空间中定义的,或者说标准头文件(如iostream)中的函数、类、对象、类模板是在命名空间std中定义的。
- 在该标识符前面加上命名空间及作用域符号“::”,如: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的访问信息
- max_size()返回vector可以容纳元素的数量。
- size()返回vector当前元素的数量。
- capacity()返回所能容纳的元素数量(在不重新分配内存的情况下)
- empty()判断是非空,为空返回ture,否则flase。
- 存取vector元素
存储vector信息都可以使用构造函数、push_back()、insert()、数组运算符、赋值运算符、pop_back()、erase()、begin()、end()、rbegin()、size()、maxsize等、
- 关于运算符
==、!=、<=、>=、<、>、vector之前大小的比较是按照词典规则,要访问vector中的某特定位置的元素可以使用[]操作。
如果两个vector具有相同的容量。且所有相同位置的元素都相同,则这两个vector被认为是相等的。如果vector用来存储用户自定义类的对象,必须重载“==”和“<” 运算符。