树是最基本的一种数据结构,作为一个程序员我打算花几篇文章来谈一下我对树的理解,希望对自己学的东西进行一个总结和实践。
那么我打算线从树的基本机构开始讲起,这篇文章我会对一般的树和表达式树进行讲解,(表达式树是所谓分析树的一个小例子,是编译器设计的核心数据结构,如果有时间我会好好看一下分析树的数据结构并且实践一下写一篇总结文章)。后面我会做一些关于ADT以及ALV到红黑树的一些实践。
什么是树?
1.有向边和节点的集合,边是有向边。一棵树有n个节点,那么就有n-1条边,这个很好理解,因为除了根节点没有变,其他节点都有边。
2.一棵树可以有多棵子树所构成。
3.树里面包含父节点和子节点,没有子节点的节点称为叶子节点。
4.一棵树,到每一个节点恰好存在一个路径,这时候会想到大学里学哈夫曼树。
5.树的深度:根到叶子节点的最大路径长即为深度。
常见的数据结构
typedef struct TreNode *PtrToNode;
struct TreeNode{
ElementType Element;
PtrToNode FirstClid; //指向第一个孩子节点的指针
PtrToNode NextSibling; //指下一个兄弟节点的指针
}
如果构建一个树,它可能直观上看是这样的
但是再数据结构中是这样的
关于树的遍历
树的遍历主要包括先序遍历和后序遍历
先序就是线遍历根再遍历叶子节点,后序遍历就是先遍历叶子节点再遍历根节点。常见的写法就是递归了。所以也就有了我们关于二叉树遍历时,那几句熟悉的话。(先序遍历:根左右,中序遍历:左右根,后序遍历:左右根)。
//伪代码
void function(TreeNode* n){
do_something(n); //.......................<1>
if(n has child){ //.....................<2>
for( each c child of n){
function(c);
}
}
}
最直观的理解就是,先序<1>在前<2>在后,后序则反之。
在Unix等很多操作系统中的目录结构其实算是树形结构,那么我就写了一个简单的代码来模拟一个目录结构。
/**
* 内容:模拟文件目录系统
* 用于展示多叉树 :前序遍历 和 后序遍历
*/
#include<iostream>
#include<string>
#define FILE 0
#define FLOLDER 1
using namespace std;
typedef struct _TreeNode1 TreeNode1;
/**
* 文件节点的数据结构
* type:文件类型
* size:文件大小
* data:文件名或者文件夹名
* FirstChild : 孩子链表
* NextSibling :兄弟链表
*/
struct _TreeNode1{
int type;
int size;
string name;
TreeNode1* FirstChild;
TreeNode1* NextSibling;
_TreeNode1(int t, int s, string n):type(t), size(s), name(n),FirstChild(NULL),NextSibling(NULL){}
void printTreeNode(int deep, bool flag){
for(int i = 0; i < deep; ++i) cout << " " <<ends;
cout << name <<endl;
}
};
class demo1
{
private:
TreeNode1* root; //根节点
/**
* 创建假树
*/
TreeNode1* creatTree();
/**
* 先序遍历,打印出目录
*/
void ListDir(TreeNode1* root, int deep);
/**
* 后序遍历,求解整个目录大小
*/
int getSize(TreeNode1* root, int deep);
/**
* 递归释放内存
*/
void release(TreeNode1* p);
public:
demo1();
~demo1();
void run();
};
这是头文件
void ListDir(TreeNode1* root, int deep);
该接口负责先序遍历树
int getSize(TreeNode1* root, int deep);
该接口负责后序遍历树
void demo1::ListDir(TreeNode1* root, int deep){
root->printTreeNode(deep, false); //这里就是我们刚才伪代码中的dosomething
TreeNode1* children = root->FirstChild;
while(children != NULL){
ListDir(children, deep + 1);
children = children->NextSibling;
}
}
int demo1::getSize(TreeNode1* p, int deep){
int ret = p->size;
TreeNode1* children = p->FirstChild;
while (children)
{
ret = getSize(children, deep + 1) + ret;
children = children->NextSibling;
}
p->printTreeNode2(deep); //这里就是我们刚才伪代码中的dosomething
return ret;
}
void demo1::release(TreeNode1* p){
TreeNode1* child = p->FirstChild;
while(child != NULL){
release(child);
child = child->NextSibling;
}
delete p;
}
可以看出这里时简单的一个递归去对树完成了遍历,很多时候我们都会采用递归的方式遍历树,当然也可以借助一些数据结构不去递归遍历树,我会把我以后包括这次实践的代码放到git lab上,以供以后回顾。我这里使用的是gcc对文件编译的。
编译命令如下
cd out
rm *
cd ..
cd src
g++ -c demo1.cpp -o ../out/demo1.o -I ./include
g++ -c demo2.cpp -o ../out/demo2.o -I ./include
g++ main.cpp -o ../out/tree -ldl ../out/demo1.o ../out/demo2.o -I ./include
cd ../out
./tree
运行结果
表达式树(二叉树的一种)
表达式树是二叉树的一种,它可以用在对公式的计算当中去,因为加减乘除的操作都是二元的所以它正好是一个二叉树。
其中叶子节点都是数字,其他节点为操作符。如图所示为一个简单的操作树:
它表示的是2*(2+3) 结果是3
那么下面的实践内容就是 通过用户的输入计算表达式来构建一个表达式树,并且计算它的内容
首先补充两个内容,在计算器中,一半使用后最表达式构建表达式树,这涉及到中缀转后缀的一个过程 :
1.使用一个栈来记录数字(结果)记为S1,一个栈来记录运算符S2。如果是数字那么直接进入S1,如果是运算符,那么进入S2.但是在进入S2时,
- 如果S2为空,那么直接进入S2
- 如果S2不为空,那么判断运算符的优先级,优先级大那么直接插入。否则弹出前面大于等于当前优先级的运算符。
-
'*' == '/' > '+ '== '-'
2.关于括号‘(’的优先级最高,‘)’会弹出运算符直到遇到‘(’。且括号不会输出到后最表达式中。
第二点时关于后最表达式树的构建,借助了栈的空间,其中储存树的根作为元素。
- 遇到数字则新建一个树。
- 遇到运算符,则构建节点合并栈中前两个树,一个作为左子树,一个作为右指数。
如图
头文件:
其中info式在上图中的 像2或者3这样的信息,info为存放的信息在实际的节点中即Node2。它可以式运算符也可以式数字。
/**
* a. 中缀表达式 => 后缀表达式
* b. 用表达式树计算 中缀表达式的结果
*/
#include<iostream>
#include<string>
#define NUMBER 0
#define SYMBOL 1
using namespace std;
/**
* 二叉树节点信息
* NUMBER - 数字
* SYMBOL - 运算符
*/
typedef struct _Info2
{
int type;
typedef union data
{
int num;
char symbol;
}Data;
Data mData;
_Info2(int t, int n, char s):type(t){
switch (type)
{
case NUMBER:
mData.num = n;
break;
case SYMBOL:
mData.symbol = s;
break;
default:
cout << "[Warn]: unknow data type" << endl;
break;
}
}
}Info2;
/**
* 返回当前运算符的优先级大小
* ps: 对于‘(’ 优先级最高 ')' 我会特殊处理
*/
int getOrder(char c){
switch (c)
{
case '+':
return 0;
case '-':
return 0;
case '*':
return 1;
case '/':
return 1;
case '(':
return 2;
default:
return -1;
}
}
typedef struct _Node2 Node2;
struct _Node2
{
Info2 info;
Node2* left;
Node2* right;
_Node2(Info2 i):left(NULL),right(NULL),info(i){
}
};
class demo2
{
private:
Node2* root;
public:
demo2();
/**
* 输入中缀表达式
* 打印后缀表达式,构建info栈
*/
void input();
/**
* 通过info栈构建 后缀表达式树
*/
void create();
/**
* 通过表达式树,计算表达式值
*/
int caculate();
void run();
~demo2();
};
#include "demo2.h"
#include <stack>
demo2::demo2():root(NULL){
}
demo2::~demo2(){
}
/**
* 输入中缀表达式
* 打印后缀表达式,构建info栈
*/
void demo2::input(){
string data;
cout << "请输入您计算的公式"<< endl;
cin >> data;
stack<char> syms; //temp symbol stack
vector<Info2> infos;
if(data[0] == '-'){
infos.push_back(Info2(NUMBER,0,0));
}
int i = 0;
while(i < data.size()){
if ( data[i] > '0' && data[i] <= '9'){
//handle num
int n = 0;
while(data[i] >= '0' && data[i] <= '9'){
n = n * 10 + data[i++] - '0';
}
infos.push_back(Info2(NUMBER,n,0));
}else{
//handle char
if(syms.empty()){
syms.push(data[i]);
}else{
if(data[i] == ')'){
//handle case ( )
while (syms.top() != '(')
{
infos.push_back(Info2(SYMBOL,0, syms.top()));
syms.pop();
}
syms.pop();//pop '('
}else{
if( getOrder(data[i]) > getOrder(syms.top()) ){
//insert to the stack directly
syms.push(data[i]);
}else{
//pop stack until empty or lower priorty
while(!syms.empty() && ( getOrder(data[i]) <= getOrder(syms.top()) ) ){
if(syms.top() == '(') break;
infos.push_back(Info2(SYMBOL, 0, syms.top()));
syms.pop();
}
syms.push(data[i]);
}
}
}
i++;
}
}
while(!syms.empty()){
infos.push_back(Info2(SYMBOL, 0, syms.top()));
syms.pop();
}
cout << "转化为后缀表达式为:" << endl;
//output stack
for(int j = 0; j < infos.size(); ++j){
switch (infos[j].type)
{
case NUMBER:
cout << infos[j].mData.num << " " << ends;
break;
case SYMBOL:
cout << infos[j].mData.symbol << " " << ends;
break;
}
}
//create tree
cout << "\n.................\n" << endl;
create(infos);
}
/**
* 通过info栈构建 后缀表达式树
* 可以参考上图
*/
void demo2::create(vector<Info2>& infos){
stack<Node2*> nodeStack;
for(vector<Info2>::iterator it = infos.begin(); it != infos.end(); ++it){
if(it->type == NUMBER)
{
Node2* n = new Node2(*it);
nodeStack.push(n);
}else if(it->type == SYMBOL){
Node2* n = new Node2(*it);
//因为第二个操作数 后如栈
Node2* b = nodeStack.top(); nodeStack.pop();
Node2* a = nodeStack.top(); nodeStack.pop();
n->left = a; n->right = b;
nodeStack.push(n);
}
}
if(!nodeStack.empty()) root = nodeStack.top();
cout << "表达式树构建完成, 大小: " << nodeStack.size() << endl;
}
/**
* 通过表达式树,计算表达式值
* 递归求解
*/
static int search(Node2* n){
if(n == NULL) return 0;
if(n->info.type == NUMBER){
return n->info.mData.num;
}
char m_operator = n->info.mData.symbol;
if( m_operator == '+')
return search(n->left) + search(n->right);
if( m_operator == '-')
return search(n->left) - search(n->right);
if( m_operator == '*')
return search(n->left) * search(n->right);
if( m_operator == '/')
return search(n->left) / search(n->right);
return -1;
}
int demo2::caculate(){
int ret = search(root);
cout << "计算结果:" << ret << endl;
}
void demo2::run(){
cout << "Demo begin>>>>" << endl;
input();
caculate();
cout << "\nDemo end<<<<" << endl;
}
运行结果
可以看到通过表达式树我们正确求解了,上面比较复杂的一个运算公式。
以上就是本次的全部内容,接下来我会写一下关于ALV树的建立,增,删操纵,和它是如何保持平衡的内容。
2020年2月25日