【数据结构】树笔记
创始人
2024-05-29 02:54:37
0
  1. 基础知识

树,有父节点和子节点。

二叉树分为:满二叉树,完全二叉树,完美二叉树

满二叉树:在当前树中,父节点不存在缺失左儿子或右儿子的现象。

完全二叉树:基于满二叉树,当前树中从上往下,从左至右数结点,不存在跳过空结点的现象

完美二叉树:基于完全二叉树,根节点左右子树等高

  1. 二叉树

1. 创建二叉树

树的开始处是根节点,结点可能有两个子节点。

#include
using namespace std;
template
struct p{t data;p *left_p;p*right_p;p(t num){left_p=NULL;right_p=NULL;data=num;}
};

然后尝试设置一个初始化树的函数。

template
class tree{
private:p *father;
public:tree(t n){father=new p(n);}~tree(){}

树初始化好了,出现了new结点,所以将来还要记得把树销毁掉,即析构函数。

2. 添加新结点

首先思考需要怎样才能添加一个确定的(或者说符合用户心意的)结点。

新结点的值是必定的,还需要指定新结点的父节点,并指定是要插入父节点的左子树还是右子树。

不能让用户自己创建一个结点指定,用户的输入必须简单。这里我们假设这棵树中无重复值,所以用户需要输入的树中目标结点的值。

这里涉及到一个问题,如何找到这个目标结点。于是我们先写查找结点的函数。

3. 查找结点

遍历树:先看当前结点的左子树,看完且没有找到后再看右子树->典型的递归函数

template
class tree{
private:p *father;p* refind(t n,p*r){if(r==NULL){return NULL;}if(r->data==n){return r;}p*temp_p=refind(n,r->left_p);if(temp_p==NULL){temp_p=refind(n,r->right_p);}return temp_p;}
public:tree(t n){father=new p(n);}~tree(){}p* findn(t n){return refind(n,father);}
};

回过头来说插入结点。

找到之后,首先看,如果我们要插入的是左结点,那看找到的这个结点它左结点满没满,没满,插入,满了,插入失败,插入右结点同理

int insert(t n,int flag,t num){p*temp_p=refind(n,father);if(temp_p==NULL){return 0;}if(flag==0){if(temp_p->left_p==NULL){p*new_p=new p(num);temp_p->left_p=new_p;return 1;}return 0;}else{if(temp_p->right_p==NULL){p*new_p=new p(num);temp_p->right_p=new_p;return 1;}return 0;}}

4. 遍历二叉树

已经写完了查找,遍历是一样的。

template
class tree{
private:p *father;void repre(p*temp_p){if(temp_p==NULL ){return;}cout<data<<" ";repre(temp_p->left_p);repre(temp_p->right_p);}
public:tree(t n){father=new p(n);}~tree(){}void pre(){repre(father);cout<

以上方法是前序遍历。前序遍历就是:输出顺序为:父节点,左子树,右子树

后面还有中序遍历,后序遍历。

中序遍历输出顺序为:左子树,父节点,右子树

后序遍历就是:左子树,右子树,根节点。

很明显这两个和前序遍历写法一样,除了要移动一下输出的位置。

#include
using namespace std;
template
struct p{t data;p *left_p;p*right_p;p(t num){left_p=NULL;right_p=NULL;data=num;}
};
template
class tree{
private:p *father;void repre(p*temp_p,int n){for(int i=0;idata<left_p==NULL and temp_p->right_p==NULL){return;}repre(temp_p->left_p,n+2);repre(temp_p->right_p,n+2);}void remi(p*temp_p){//中序递归if(temp_p==NULL){return ;}remi(temp_p->left_p);cout<data<<" ";remi(temp_p->right_p);}void relast(p*temp_p){//后序递归if(temp_p==NULL){return;}relast(temp_p->left_p);relast(temp_p->right_p);cout<data<<" ";}p* refind(t n,p*r){if(r==NULL){return NULL;}if(r->data==n){return r;}p*temp_p=refind(n,r->left_p);if(temp_p==NULL){temp_p=refind(n,r->right_p);}return temp_p;}
public:tree(t n){father=new p(n);}~tree(){}void preprint(){//前序repre(father,0);}void midprint(){//中序remi(father);cout<* findn(t n){return refind(n,father);}int insert(t n,int flag,t num){p*temp_p=refind(n,father);if(temp_p==NULL){return 0;}if(flag==0){if(temp_p->left_p==NULL){p*new_p=new p(num);temp_p->left_p=new_p;return 1;}return 0;}else{if(temp_p->right_p==NULL){p*new_p=new p(num);temp_p->right_p=new_p;return 1;}return 0;}}
};
int main(){treet(11);t.insert(11,0,22);t.insert(11,1,33);t.insert(22,0,44);t.insert(33,0,55);t.preprint();t.midprint();t.lastprint();
}

虽然上述都是用递归实现的,但如果树过大,系统的栈空间已满,递归将失败。所以考虑用迭代实现。

递归其实也是用栈实现的,所以说,我们完全可以用栈来代替递归过程

前序:(翻译一下递归的实现方式就好了)

void repre(){stack*>s;s.push(father);while(!s.empty()){p *temp_p=s.top();s.pop();cout<data<<" ";if(temp_p->right_p!=NULL){s.push(temp_p->right_p);}if(temp_p->left_p!=NULL){s.push(temp_p->left_p);}}}

中序:一个数要访问两次才能输出,所以再拿一个栈存。不管右结点有没有,都把它加入。

翻译一下当时做递归中序时的方法:只有空的时候才返回,说迭代的语言就是,只有空的时候才弹栈。

(这样才能越级输出根节点)

void remid(){stack*>s;//存放第一次遍历的结点stack*>s1;//存放第二次遍历的结点s.push(father);while(!s.empty()){p*temp_p=s.top();s.pop();if(temp_p==NULL){if(!s1.empty()){cout<data<<" ";s1.pop();}}else if (temp_p->left_p==NULL and temp_p->right_p==NULL) {cout<data<<" ";cout<data<<" ";s1.pop();}else{s1.push(temp_p);s.push(temp_p->right_p);s.push(temp_p->left_p);}}

后序遍历的迭代方法和中序遍历是一样的,关键点是处理什么时候彻底弹栈(即弹出s1栈)。本来想设置两个栈的,后来意识到其实我们是没有办法区分左右子树的。也就是,弹主栈的操作遇到两次,s1就要弹出一个。可以自己画图试着理解。

void relast(){stack*>s;stack*>s1;int cont=0;s.push(father);while(!s.empty()){p*temp_p=s.top();s.pop();if(temp_p==NULL){cont++;if(cont==2){if(!s1.empty()){cout<data<<" ";s1.pop();cont=1;}}}else{s1.push(temp_p);s.push(temp_p->right_p);s.push(temp_p->left_p);}}cout<

下次来补层序遍历。。。受不了了,敲不动了。

  1. 二叉查找树(BST)

这棵树左节点小于根节点,右节点大于根节点。

所以看起来,找的时候只要向一边找就行了,每次舍弃掉一半的点,算法课算过,效率是O(lgn)。

但是如果树长得很丑,比如全部只有右子树(退化成链表),这样就不存在舍弃点,所以效率就退化回O(n)

相关内容

热门资讯

圆桌讨论:Circle 与 S... 编译 | 深潮 TechFlow该播客来源于 6 月 13 日的 Unchained,部分信息可能存...
南浔以科技“链”动农业新图景 转自:湖州日报  记者  汉霖  通讯员  费腾辉  本报讯  记者日前走进位于南浔区和孚镇温氏鸡养...
市审计局切实增强数据分析团队业...   本报讯 (通讯员 张晨)为积极践行“科技强审”路径,建立学习、培训、交流工作机制,增强数据分析团...
中信建投:2025年炎夏侵袭全... 格隆汇7月7日|中信建投研报表示,2025年炎夏侵袭全球,空调需求景气向好。今年夏季极端高温天气频发...
顾家家居质押触发鹰眼“高风险”... 截止2025年7月5日,顾家家居整体质押股份为3.10亿股,整体质押占总股本之比为37.67%,累计...
激智科技质押触发鹰眼“风险”评... 截止2025年7月5日,激智科技整体质押股份为1694.00万股,整体质押占总股本之比为6.42%,...
道恩股份质押触发鹰眼“风险”评... 截止2025年7月5日,道恩股份整体质押股份为1.59亿股,整体质押占总股本之比为33.20%,累计...
和胜股份核心股东质押股价疑似触... 截止2025年7月5日,和胜股份(维权)整体质押股份为4089.00万股,整体质押占总股本之比为14...
华神科技质押触发鹰眼“风险”评... 截止2025年7月5日,华神科技整体质押股份为8903.36万股,整体质押占总股本之比为14.27%...
陈国厂:救急更要谋长远    陈国厂(右)在医养院和老人攀谈。   2025年全国两会期间,陈国厂接受媒体采访。  陈国厂 ...