树的基本概念和遍历规则 数据结构和算法 二叉树遍历(前序、中序、后序、层次、深度优先、广度优先遍历)

zsychanpin

树的基本概念和遍历规则

树的递归定义

树是n(n>0)个结点的有限集,这个集合满足下面条件:
      ⑴有且仅有一个结点没有前驱(父亲结点)。该结点称为树的根。
      ⑵除根外,其余的每一个结点都有且仅有一个前驱;
      ⑶除根外,每个结点都通过唯一的路径连到根上(否则有环)。

这条路径由根開始,而未端就在该结点上,且除根以外,路径上的每个结点都是前一个结点的后继(儿子结点);
由上述定义可知,树结构没有封闭的回路。 

节点的分类

结点一般分成三类

⑴根结点:没有父亲的结点。

在树中有且仅有一个根结点。

如节点r
⑵分支结点:除根结点外,有孩子的结点称为分支结点。如a,b,c,x,t,d,i
⑶叶结点:没有孩子的结点称为树叶。如w,h,e,f,s,m,o,n,j,u
根结点到每个分支结点或叶结点的路径是唯一的。
从根r到结点i的唯一路径为rcti。




树的度

⑴结点的度:一个结点的子树数目称为该结点的度。如结点i的度为3。节点t的度为2,节点b的度为1。

显然,全部树叶的度为0。
⑵树的度:全部结点中最大的度称为该树的度(宽度)。

            下列树的度为3。

假设採用数组存储子节点地址的话,则应依据树的度定义数组大小

 

树的深度

 树是分层次的。结点所在的层次是从根算起的。

根结点在第一层,根的儿子在第二层,其余各层依次类推。

即某个节点在第k层,则该节点的后件均在第k+1层。
在树中。父结点在同一层的全部结点构成兄弟关系。


树中最大的层次称为树的深度。亦称高度。

图中树的深度为5。

 

森林

所谓森林。是指若干棵互不相交的树的集合。

如图去掉根结点。其原来的三棵子树Ta,Tb,Tc的集合{Ta。Tb。Tc}就为森林。这三棵子树的详细形态例如以下:

 

有序树和无序树

依照树中同层结点是否保持有序性,可将树分为有序树和无序树。


    (1)假设树中同层结点从左而右排列,其次序不容互换。这种树称为有序树;
    (2)假设同层结点的次序随意,这种树称为无序树。

 


树的表示方法

 ⑴自然界的树形表示法:
用结点和边表示树,比如上图採用的就是自然界的树形表示法。树形表示法一般用于分析问题。


长处:直观,形象;缺点:保存困难。

⑵括号表示法:
先将根结点放入一对圆括号里,然后把它的子树按由左而右的顺序放入括号里。而对子树也採用相同方法处理:同层子树与它的根结点用圆括号括起来。同层子树之间用逗号隔开,最后用闭括号括起来。比如图可写成例如以下形式
(A(B(E(K,L),F),C(G),D(H(M),I,J))) 
长处:易于保存;缺点:不直观


 

树的遍历规则

所谓树的遍历,是指依照一定的规律不反复地訪问(或取出节点中的信息,或对节点做其它的处理)树中的每个节点,其遍历过程实质上是将树这样的非线性结构按一定规律转化为线性结构。



先根次序遍历
后根次序遍历

先根次序遍历树

先根次序遍历的遍历规则为:若树为空。则退出;否则先根訪问树的根点,然后先根遍历根的每棵子树。

比如。对右图所看到的树进行先根次序遍历,形成的次序为:
Rawxdhebfcstimonju

算法为:
program preorder(v:integer);
{訪问处理节点v;
for i in adj(v) do if i未訪问 then  preorder(i);
}

后根次序遍历树

后根次序遍历的遍历规则为:若树为空,则退出;否则后根訪问每棵子树,然后訪问根节点。比如,对右图所看到的树进行后根次序遍历,形成的次序为:
whdexafbsmonijtucR

算法为:
program postorder(v:integer);
{for i in adj(v) do if i未訪问 then  postorder(i);
訪问处理节点v;
}

【众安尊享e生】-国民百万医疗保险,投保详解及案例分析,每年最低112元——马云杀手锏

好文要顶 关注我 收藏该文  

zsychanpin
关注 - 0
粉丝 - 7

+加关注

« 上一篇:结合源代码分析android的消息机制
» 下一篇:Scala环境搭建之eclipse

posted @ 2017-07-15 13:38 zsychanpin 阅读(5847) 评论(0) 编辑 收藏

https://www.cnblogs.com/zsychanpin/p/7182467.html

llguanli

二叉树遍历(前序、中序、后序、层次、深度优先、广度优先遍历)

二叉树是一种非常重要的数据结构,非常多其他数据结构都是基于二叉树的基础演变而来的。对于二叉树,有深度遍历和广度遍历,深度遍历有前序、中序以及后序三种遍历方法,广度遍历即我们寻常所说的层次遍历。由于树的定义本身就是递归定义,因此採用递归的方法去实现树的三种遍历不仅easy理解并且代码非常简洁,而对于广度遍历来说,须要其他数据结构的支撑。比方堆了。所以。对于一段代码来说,可读性有时候要比代码本身的效率要重要的多。

四种基本的遍历思想为:

前序遍历:根结点 ---> 左子树 ---> 右子树

中序遍历:左子树---> 根结点 ---> 右子树

后序遍历:左子树 ---> 右子树 ---> 根结点

层次遍历:仅仅需按层次遍历就可以

比如。求以下二叉树的各种遍历

前序遍历:1  2  4  5  7  8  3  6 

中序遍历:4  2  7  5  8  1  3  6

后序遍历:4  7  8  5  2  6  3  1

层次遍历:1  2  3  4  5  6  7  8

一、前序遍历

1)依据上文提到的遍历思路:根结点 ---> 左子树 ---> 右子树,非常easy写出递归版本号:

[java] view plain copy

  1. public void preOrderTraverse1(TreeNode root) {  
  2.         if (root != null) {  
  3.             System.out.print(root.val+"  ");  
  4.             preOrderTraverse1(root.left);  
  5.             preOrderTraverse1(root.right);  
  6.         }  
  7.     }  

2)如今讨论非递归的版本号:

依据前序遍历的顺序,优先訪问根结点。然后在訪问左子树和右子树。所以。对于随意结点node。第一部分即直接訪问之,之后在推断左子树是否为空,不为空时即反复上面的步骤,直到其为空。若为空。则须要訪问右子树。注意。在訪问过左孩子之后。须要反过来訪问其右孩子。所以,须要栈这样的数据结构的支持。对于随意一个结点node,详细过程例如以下:

a)訪问之,并把结点node入栈。当前结点置为左孩子;

b)推断结点node是否为空,若为空。则取出栈顶结点并出栈,将右孩子置为当前结点;否则反复a)步直到当前结点为空或者栈为空(能够发现栈中的结点就是为了訪问右孩子才存储的)

代码例如以下:

[java] view plain copy

  1. public void preOrderTraverse2(TreeNode root) {  
  2.         LinkedList<TreeNode> stack = new LinkedList<>();  
  3.         TreeNode pNode = root;  
  4.         while (pNode != null || !stack.isEmpty()) {  
  5.             if (pNode != null) {  
  6.                 System.out.print(pNode.val+"  ");  
  7.                 stack.push(pNode);  
  8.                 pNode = pNode.left;  
  9.             } else { //pNode == null && !stack.isEmpty()  
  10.                 TreeNode node = stack.pop();  
  11.                 pNode = node.right;  
  12.             }  
  13.         }  
  14.     }  


二、中序遍历

1)依据上文提到的遍历思路:左子树 ---> 根结点 ---> 右子树,非常easy写出递归版本号:

[java] view plain copy

  1. public void inOrderTraverse1(TreeNode root) {  
  2.         if (root != null) {  
  3.             inOrderTraverse1(root.left);  
  4.             System.out.print(root.val+"  ");  
  5.             inOrderTraverse1(root.right);  
  6.         }  
  7.     }  


2)非递归实现,有了上面前序的解释,中序也就比較简单了。同样的道理。仅仅只是訪问的顺序移到出栈时。代码例如以下:

[java] view plain copy

  1. public void inOrderTraverse2(TreeNode root) {  
  2.         LinkedList<TreeNode> stack = new LinkedList<>();  
  3.         TreeNode pNode = root;  
  4.         while (pNode != null || !stack.isEmpty()) {  
  5.             if (pNode != null) {  
  6.                 stack.push(pNode);  
  7.                 pNode = pNode.left;  
  8.             } else { //pNode == null && !stack.isEmpty()  
  9.                 TreeNode node = stack.pop();  
  10.                 System.out.print(node.val+"  ");  
  11.                 pNode = node.right;  
  12.             }  
  13.         }  
  14.     }  

三、后序遍历

1)依据上文提到的遍历思路:左子树 ---> 右子树 ---> 根结点。非常easy写出递归版本号:

[java] view plain copy

  1. public void postOrderTraverse1(TreeNode root) {  
  2.         if (root != null) {  
  3.             postOrderTraverse1(root.left);  
  4.             postOrderTraverse1(root.right);  
  5.             System.out.print(root.val+"  ");  
  6.         }  
  7.     }  

2)

后序遍历的非递归实现是三种遍历方式中最难的一种。由于在后序遍历中,要保证左孩子和右孩子都已被訪问而且左孩子在右孩子前訪问才干訪问根结点,这就为流程的控制带来了难题。以下介绍两种思路。

      第一种思路:对于任一结点P,将其入栈,然后沿其左子树一直往下搜索。直到搜索到没有左孩子的结点,此时该结点出如今栈顶,可是此时不能将其出栈并訪问,因此其右孩子还为被訪问。

所以接下来依照同样的规则对其右子树进行同样的处理,当訪问完其右孩子时。该结点又出如今栈顶,此时能够将其出栈并訪问。这样就保证了正确的訪问顺序。能够看出,在这个过程中,每一个结点都两次出如今栈顶,仅仅有在第二次出如今栈顶时,才干訪问它。因此须要多设置一个变量标识该结点是否是第一次出如今栈顶。

复制代码

复制代码

void postOrder2(BinTree *root)    //非递归后序遍历
{
    stack<BTNode*> s;
    BinTree *p=root;
    BTNode *temp;
    while(p!=NULL||!s.empty())
    {
        while(p!=NULL)              //沿左子树一直往下搜索。直至出现没有左子树的结点 
        {
            BTNode *btn=(BTNode *)malloc(sizeof(BTNode));
            btn->btnode=p;
            btn->isFirst=true;
            s.push(btn);
            p=p->lchild;
        }
        if(!s.empty())
        {
            temp=s.top();
            s.pop();
            if(temp->isFirst==true)     //表示是第一次出如今栈顶 
             {
                temp->isFirst=false;
                s.push(temp);
                p=temp->btnode->rchild;    
            }
            else                        //第二次出如今栈顶 
             {
                cout<<temp->btnode->data<<" ";
                p=NULL;
            }
        }
    }    
} 

复制代码

复制代码

        另外一种思路:要保证根结点在左孩子和右孩子訪问之后才干訪问,因此对于任一结点P。先将其入栈。假设P不存在左孩子和右孩子。则能够直接訪问它;或者P存在左孩子或者右孩子。可是其左孩子和右孩子都已被訪问过了。则相同能够直接訪问该结点。若非上述两种情况。则将P的右孩子和左孩子依次入栈。这样就保证了每次取栈顶元素的时候,左孩子在右孩子前面被訪问。左孩子和右孩子都在根结点前面被訪问。

复制代码

复制代码

void postOrder3(BinTree *root)     //非递归后序遍历
{
    stack<BinTree*> s;
    BinTree *cur;                      //当前结点 
    BinTree *pre=NULL;                 //前一次訪问的结点 
    s.push(root);
    while(!s.empty())
    {
        cur=s.top();
        if((cur->lchild==NULL&&cur->rchild==NULL)||
           (pre!=NULL&&(pre==cur->lchild||pre==cur->rchild)))
        {
            cout<<cur->data<<" ";  //假设当前结点没有孩子结点或者孩子节点都已被訪问过 
              s.pop();
            pre=cur; 
        }
        else
        {
            if(cur->rchild!=NULL)
                s.push(cur->rchild);
            if(cur->lchild!=NULL)    
                s.push(cur->lchild);
        }
    }    
}

复制代码

复制代码

四、层次遍历

层次遍历的代码比較简单。仅仅须要一个队列就可以。先在队列中增加根结点。之后对于随意一个结点来说。在其出队列的时候,訪问之。同一时候假设左孩子和右孩子有不为空的。入队列。代码例如以下:

[java] view plain copy

  1. public void levelTraverse(TreeNode root) {  
  2.         if (root == null) {  
  3.             return;  
  4.         }  
  5.         LinkedList<TreeNode> queue = new LinkedList<>();  
  6.         queue.offer(root);  
  7.         while (!queue.isEmpty()) {  
  8.             TreeNode node = queue.poll();  
  9.             System.out.print(node.val+"  ");  
  10.             if (node.left != null) {  
  11.                 queue.offer(node.left);  
  12.             }  
  13.             if (node.right != null) {  
  14.                 queue.offer(node.right);  
  15.             }  
  16.         }  
  17.     }  


五、深度优先遍历

事实上深度遍历就是上面的前序、中序和后序。可是为了保证与广度优先遍历相照顾,也写在这。代码也比較好理解,事实上就是前序遍历,代码例如以下:

[java] view plain copy

  1. public void depthOrderTraverse(TreeNode root) {  
  2.         if (root == null) {  
  3.             return;  
  4.         }  
  5.         LinkedList<TreeNode> stack = new LinkedList<>();  
  6.         stack.push(root);  
  7.         while (!stack.isEmpty()) {  
  8.             TreeNode node = stack.pop();  
  9.             System.out.print(node.val+"  ");  
  10.             if (node.right != null) {  
  11.                 stack.push(node.right);  
  12.             }  
  13.             if (node.left != null) {  
  14.                 stack.push(node.left);  
  15.             }  
  16.         }  
  17.     }  

好文要顶 关注我 收藏该文  

llguanli
关注 - 0
粉丝 - 2

+加关注

« 上一篇:Swift的基础,操作符,字符串和集合类型
» 下一篇:HDU 1260

posted @ 2017-08-15 10:17 llguanli 阅读(35838) 评论(0) 编辑 收藏

https://www.cnblogs.com/llguanli/p/7363657.html

猜你喜欢

转载自blog.csdn.net/xuheng8600/article/details/85307407