树,有父节点和子节点。
二叉树分为:满二叉树,完全二叉树,完美二叉树
满二叉树:在当前树中,父节点不存在缺失左儿子或右儿子的现象。
完全二叉树:基于满二叉树,当前树中从上往下,从左至右数结点,不存在跳过空结点的现象
完美二叉树:基于完全二叉树,根节点左右子树等高
树的开始处是根节点,结点可能有两个子节点。
#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结点,所以将来还要记得把树销毁掉,即析构函数。
首先思考需要怎样才能添加一个确定的(或者说符合用户心意的)结点。
新结点的值是必定的,还需要指定新结点的父节点,并指定是要插入父节点的左子树还是右子树。
不能让用户自己创建一个结点指定,用户的输入必须简单。这里我们假设这棵树中无重复值,所以用户需要输入的树中目标结点的值。
这里涉及到一个问题,如何找到这个目标结点。于是我们先写查找结点的函数。
遍历树:先看当前结点的左子树,看完且没有找到后再看右子树->典型的递归函数
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;}}
已经写完了查找,遍历是一样的。
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<
下次来补层序遍历。。。受不了了,敲不动了。
这棵树左节点小于根节点,右节点大于根节点。
所以看起来,找的时候只要向一边找就行了,每次舍弃掉一半的点,算法课算过,效率是O(lgn)。
但是如果树长得很丑,比如全部只有右子树(退化成链表),这样就不存在舍弃点,所以效率就退化回O(n)