终于到了非线性、层次关系的数据结构——树了,所有的数据结构里,最不熟悉的就是树和图,今天就先来好好回顾一下树。
在这不想写太多基础概念,因为概念上的东西百度一下就有,不再赘述。
看过基本概念后,我认为有三个地方值得注意:
1.树描述了非线性、层次性的数据
2.树具有递归特性,即树的子树与其结构类似。
3.树的数学表达,层次括号法
那么问题来了,树的具体应用背景是什么?之前的表,栈,队列都有着明确的对应的实际应用场景,树的应用背景我一时想不到,大概搜了一下,发现树的应用场景还是很多的:
公司组织架构:董事长-CXO-总监-经理-主管-员工;
中国行政区域划分:中国-省-市(县)-街道(小区)-门牌号;
汽车产品库:车-品牌-车系-配置。
(来自此处)
此外最经典的应用是家谱的存储,突然想起来当年c++的课上也是用家谱的存储命题。
本来打算用类实现一个简单的链式二叉树,突然觉得一个结点只能由两个子节点好lowb,所以干脆用类实现一个任意叉树吧。
分析一下和二叉树的区别,我们发现任意叉树的某个节点的子节点个数应任意,所以可以用链表来存储一个节点的子节点。这里我们用vector作为子节点地址的容器。
实现的功能:
Tree();//多叉树初始化
~Tree();//多叉树销毁
void AssignRootNode(Datatype data);//根节点赋值
void AddChildNode(string father_name, Datatype data);//增加子节点
void DeleteNode(string name);//删除节点(及其子树)
void PrintTreeNodes();//打印树的所有节点数据
void SearchKeyword(string keyword);//查找关键词
void PrintTree();//按照括号层次法打印树
话不多说上代码:
// 用类实现一个链式任意叉树,用于存储家谱信息(姓名,性别,生日,配偶)
// 成员添加方式为指定父/母姓名
#include "pch.h"
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#define Datatype Data
using namespace std;
//定义配偶信息结构体
struct Spouse
{
string name;
string gender;
string birthday;
};
//定义家谱信息结构体
struct Data
{
string name;//姓名,最多四个汉字
string gender;//性别,一个汉字
string birthday;//生日,八个字符,19970701,香港永远是中国不可分割的一部分
Spouse* spouse;//采用结构体地址,可以判断是否已婚
};
//定义多叉树的节点结构体
struct Node
{
Datatype data;//节点数据
vector<Node*> child_nodes;//子节点地址列表
Node* father_point;//父节点地址
};
//打印节点数据
void PrintNodeData(Node* point)
{
cout << point->data.name << ' ' << point->data.gender << ' ' << point->data.birthday;
if (point->data.spouse)cout << " 配偶:" << point->data.spouse->name << ' '
<< point->data.spouse->gender << ' ' << point->data.spouse->birthday << ' ' << endl;
else cout << " 无配偶" << endl;
}
//swap式删除数据
void DeleteVectorElement(vector<Node*> &vector, int i)
{
if (i == vector.size() - 1)vector.pop_back();
else {
swap(vector[i], vector.back());
vector.pop_back();
swap(vector[i], vector.back());
}
}
//定义多叉树类
class Tree
{
public:
Tree();//多叉树初始化
~Tree();//多叉树销毁
void AssignRootNode(Datatype data);//根节点赋值
void AddChildNode(string father_name, Datatype data);//增加子节点
void DeleteNode(string name);//删除节点(及其子树)
void PrintTreeNodes();//打印树的所有节点数据
void SearchKeyword(string keyword);//查找关键词
void PrintTree();//按照括号层次法打印树
private:
void subPrintTree(Node* root_point);//打印函数的辅助函数,以实现递归
Node* SearchNode(string name);//查找节点
Node* GenerateNewNode(Datatype data, Node* father_point);//根据data生成新节点
void GetAllPoints(vector<Node*> &point_list, Node* root_point);//获取所有节点地址
void DeleteNode(Node* point);//销毁节点
int node_number;//节点数
Node* root_point;//根节点地址
};
//多叉树初始化
Tree::Tree()
{
node_number = 0;
root_point = NULL;
cout << "【树已初始化】" << endl;
}
//多叉树销毁
Tree::~Tree()
{
//获取所有节点地址
vector<Node*> point_list;
GetAllPoints(point_list, root_point);
//遍历删除节点
for (auto &point : point_list)
{
DeleteNode(point);
}
cout << "【树已销毁】" << endl;
}
//根节点赋值
void Tree::AssignRootNode(Datatype data)
{
//申请内存
Node* new_node = new Node;
//data赋值
new_node->data.name = data.name;
new_node->data.gender = data.gender;
new_node->data.birthday = data.birthday;
new_node->data.spouse = data.spouse;
//指针赋值
new_node->father_point = NULL;
new_node->child_nodes.push_back(NULL);
//根节点赋值
root_point = new_node;
//修改总节点数
node_number++;
}
//增加子节点
void Tree::AddChildNode(string father_name, Datatype data)
{
//查找获取其地址
Node* point = SearchNode(father_name);
//如果没找到
if (!point)return;
//如果找到了,将其链至树上
point->child_nodes.push_back(GenerateNewNode(data, point));
//并修改树的总节点数
node_number++;
}
//打印树的所有节点数据
void Tree::PrintTreeNodes()
{
cout << "【打印所有节点】" << endl;
//获取所有节点地址
vector<Node*> all_points;
GetAllPoints(all_points, root_point);
//遍历打印
for (auto point : all_points)PrintNodeData(point);
cout << "【打印结束】" << endl;
}
//根据data生成新节点
Node* Tree::GenerateNewNode(Datatype data, Node* father_node)
{
//申请内存
Node* new_node = new Node;
//data赋值
new_node->data.name = data.name;
new_node->data.gender = data.gender;
new_node->data.birthday = data.birthday;
new_node->data.spouse = data.spouse;
//指针赋值
new_node->father_point = father_node;
new_node->child_nodes.push_back(NULL);
//返回新指针
return new_node;
}
/*深度优先,递归获取所有节点地址*/
//给出某(子)树的根节点,获取该(子)树的所有节点地址并存储在point_list中
//使用时应先初始化point_list并保证其为空
void Tree::GetAllPoints(vector<Node*> &point_list, Node* root_point)
{
//只要root_point不为空就继续递归
while (root_point)
{
//存储当前节点地址
point_list.push_back(root_point);
//深度优先,递归子节点,反向遍历,保证最后一个值为NULL
vector<Node*>::reverse_iterator rit;
for (rit = root_point->child_nodes.rbegin(); rit != root_point->child_nodes.rend(); rit++)
{
GetAllPoints(point_list, *rit);
}
return;
}
}
/*销毁节点*/
//给出节点地址,销毁该节点,销毁时先删除spouse,再删除data
void Tree::DeleteNode(Node* point)
{
if (point->data.spouse)delete point->data.spouse;
delete &(point->data);
}
//查找结点
Node* Tree::SearchNode(string name)
{
//获取所有节点地址
vector<Node*> all_points;
GetAllPoints(all_points, root_point);
//遍历查找
for (auto &point : all_points)
{
//如果找到该节点
if (point->data.name == name)
{
return point;
}
}
//如果未找到,则报错
cout << "查无此人" << endl;
return NULL;
}
//查找关键词,打印包含关键词的人员信息
void Tree::SearchKeyword(string keyword)
{
//获取所有节点地址
vector<Node*> all_points;
GetAllPoints(all_points, root_point);
//插flag以判断是否有找到结果
bool flag = false;
//遍历查找
for (auto &point : all_points)
{
//可以将人员信息的所有字符串拼接在一起,以减小if条件判断的篇幅,
//但是这样做可能导致拼接部分的误判断,因此不能采用这种写法
/*string all_str = point->data.name + point->data.gender + point->data.birthday
+ point->data.spouse->name + point->data.spouse->gender + point->data.spouse->birthday;*/
//如果无配偶,则只查找本人信息
if (!point->data.spouse)
{
if (point->data.name.find(keyword) != string::npos || point->data.gender.find(keyword) != string::npos || point->data.birthday.find(keyword) != string::npos)
{
//打印该节点
PrintNodeData(point);
//修改flag
flag = true;
}
}
//否则查找两人信息
else
{
if (point->data.name.find(keyword) != string::npos || point->data.gender.find(keyword) != string::npos || point->data.birthday.find(keyword) != string::npos ||
point->data.spouse->name.find(keyword) != string::npos || point->data.spouse->gender.find(keyword) != string::npos || point->data.spouse->birthday.find(keyword) != string::npos)
{
//打印该节点
PrintNodeData(point);
//修改flag
flag = true;
}
}
}
//如果未找到,则报错
if (!flag)cout << "【查找无结果】" << endl;
}
//删除节点(及其子树)
void Tree::DeleteNode(string name)
{
//获取节点地址
Node* point = SearchNode(name);
//如果没找到,结束函数
if (!point)return;
//如果找到了,获取该子树所有节点
vector<Node*> all_points;
GetAllPoints(all_points, point);
//记录父节点
Node* father_point = point->father_point;
//从父节点的子节点vector中删除该项
//引用该vector
vector<Node*> &vec = father_point->child_nodes;
vector<Node*>::reverse_iterator rit;
for (rit = vec.rbegin(); rit != vec.rend(); rit++)
{
if ((*rit)->data.name == name)
{
DeleteVectorElement(vec, vec.rend() - 1 - rit);
//删除元素后迭代器失效,退出循环
break;
}
}
//遍历销毁
//遍历删除节点
for (auto &point : all_points)
{
DeleteNode(point);
}
cout << "【删除成功】" << endl;
}
/*括号层次法打印树辅助函数*/
//打印规则:
//递归开始时打印“(root_name”
//循环中有值则打印,第一项打印“(name”,后面的项打印“,(name”,空项打印“)”
void Tree::subPrintTree(Node* root_point)
{
//只要root_point不为空就继续递归
while (root_point)
{
cout << "(" << root_point->data.name;
//深度优先,递归子节点,反向遍历,保证最后一个值为NULL
vector<Node*>::reverse_iterator rit;
for (rit = root_point->child_nodes.rbegin(); rit != root_point->child_nodes.rend(); rit++)
{
if (rit != root_point->child_nodes.rbegin() && rit != root_point->child_nodes.rend() - 1)
cout << ",";
subPrintTree(*rit);
}
return;
}
cout << ")";
}
//括号层次法打印树
void Tree::PrintTree()
{
subPrintTree(root_point);
cout << endl;
}
//用户输入节点数据
Datatype InputData()
{
cout << "请输入人员信息(姓名,性别,生日):" << endl;
Datatype new_data;
cin >> new_data.name >> new_data.gender >> new_data.birthday;
cout << "是否已婚?(请输入 是 或 否)" << endl;
string married;
cin >> married;
if (married == "是")
{
cout << "请输入配偶信息(姓名,性别,生日):" << endl;
Spouse* spouse = new Spouse;
cin >> spouse->name >> spouse->gender >> spouse->birthday;
new_data.spouse = spouse;
}
else new_data.spouse = NULL;
return new_data;
}
//主函数测试各功能
int main()
{
//初始化
Tree tree;
//根节点输入测试
cout << "祖先:" << endl;
tree.AssignRootNode(InputData());
//子节点输入测试
cout << "家庭成员人数:" << endl;
int n;
cin >> n;
string father_name;
for (int i = 0; i < n; i++)
{
cout << "待添加人员的父亲名字:" << endl;
cin >> father_name;
tree.AddChildNode(father_name, InputData());
}
//打印所有节点
tree.PrintTreeNodes();
//删除节点测试
cout << "删除人员:" << endl;
string name;
cin >> name;
tree.DeleteNode(name);
tree.PrintTreeNodes();
//关键词查找测试
cout << "查找关键词:" << endl;
string keyword;
cin >> keyword;
tree.SearchKeyword(keyword);
//括号层次法打印测试
tree.PrintTree();
return 0;
}
/*
输入:
老爸 男 19500101
是
老妈 女 19510101
4
老爸
儿子 男 19800101
是
儿媳 女 19800101
老爸
大儿子 男 19790101
否
老爸
女儿 女 19850101
是
女婿 男 19840101
儿子
孙子 男 20000101
否
大儿子
女婿
*/