[非原创]树和图的遍历

       备注:本文使用的范例代码来自《系统设计师教程》(王春森主编),本文性质属于我的对引用代码的注释和分析,因此并非原创。本文的部分先发表于中国编程论坛。

 

(一)用栈前序遍历树

 

对这篇文章的来源性说明:理论和代码来源,《系统设计师教程》(王春森主编),文章内容来自我对书中代码的分析和手工注释。
( 本文献给小师妹:littlehead(我是大好人))

名词:栈,遍历,前序遍历,树。

(1)准备:树的定义省略。但是由于数的定义属于递归型定义,即树的每个子节点又是一棵树,所以这个递归型的定义使对树的很多操作通常都可以用递归算法来实现。对数节点的定义,一个节点含有一个数据,这里以一个字符数据作为示范,假设一个节点最多含有M个子节点(M成为树的次数),则节点定义为:

typedef struct tnode
{
    char data;
    struct tnode *child[M];
} TNODE;

我们对用根节点指针来持有和处理一棵树:即:
TNODE *root;

前序遍历和后序遍历:遍历值得是依次输出所有节点数据。前序后序中的序指的是本节点和其子节点之间的输出顺序关系。对于普通树来说存在前序,后序两种遍历。对二叉树来说又有中序遍历。

(2)我们先给出前序遍历的伪码为:
void pre_order(TNODE *t, int m)  //m为数次数
{
    if(t!=NULL) 打印t;           //先输出本节点
    for(i=0;i<m;i++)
        pre_order(t->child[i],m); //前序输出所有子节点
}

/*--------------------------------
  root-> 1
           / | \
          2  3  4
         / \    |
        5   6   7
               / \
              8   9
----------------------------------*/

对于图1所示的树,则其前序遍历和后序遍历如下:

前序:1,2,5,6,3,4,7,8,9;
后序:5,6,2,3,8,9,7,4,1;

(3)采用栈来代替递归函数的方法如下:

方法:每出栈一个节点,打印该节点,然后则将其所有子节点逆序(从右到左)入栈。
初始栈状态:根节点入栈。栈中仅有根节点。栈顶指针top=1;
结束条件:栈为空。即栈顶指针top=0;

其相关代码如下 :

Code_使用栈前序遍历树
/*使用栈前序遍历树*/
#define M 10 /*树的次/度:最大子节点数*/
#define SN 100 /*栈最大深度*/
typedef struct tnode
{
    char data;
    struct tnode *child[M];
}TNODE;

/*t-root; m-度*/
void pre_order(TNODE *t,int m)
{
    TNODE *s[SN];/*定义栈*/
    int top=0,i;/*top-栈顶指针;*/
    if(t==NULL)
        return;
    s[top++]=t;/*根节点入栈*/
    
    while(top>0)    /*当栈不为空时:*/
    {
        t=s[--top];/*取出栈顶节点并打印输出该节点*/
        printf("%c ",t->data);
        /*将当前节点的子节点按逆序入栈*/
        for(i=m-1;i>=0;i--)
        {
            if(t->child[i])
                s[top++]=t->child[i];
        }
    }
}

遍历时的栈中节点状态如图2所示(蓝色箭头表示栈顶,每一行表示栈在当前的一个状态)。

 

(二)用队列按层次遍历树

(1)按层次遍历树。层次,即相当于资源管理器的文件夹深度,见前文图1中的纵坐标。由于在示例树中的节点编号就是按照层次来给出的,所以按层次遍历这棵树的结果就是:1,2,3,4,5,6,7,8,9。
     由于我们在遍历过程中处于树的某个局部,因此我们需要引入一个辅助数据结构来帮助我们完成遍历。考虑到层次遍历的特点是,每一层次的所有子节点应该集中在一起,当访问某个节点时,其下一层次的子节点应该处于等待被访问的状态。因此我们应该引入队列作为辅助存储。
     方法如下:

Title
     算法:每次取出队首节点,打印该节点,队首指针向后移动一格,然后将其所有子节点顺序进入列尾部,队尾指针相应向后移动。
     初始队列状态:根节点入队列,队列中仅有根节点。head=0,tail=1;
     结束条件:队列为空。即队列的两个指针重合,head==tail;
    
     队列我们同样用一个数组来模拟,并且需要两个变量head和tail来标识队列的有效区间,队列的特点是FIFO(先入先出),因此,子序列入队是顺序入队(和前文中用栈前序不同,由于栈的特点是FILO或者LIFO,所以子节点是逆序入栈的)。有效队列在数组中处于移动状态,在head,tail在移动到数组末尾时,重新指向数组前部,所以代码中这里有个索引自增后对队列长度取余的运算,因此实际上这里的队列在逻辑上是环形的,也就是数组的首尾相接,因此只有向队首或者向队尾两种移动方向的区别,而队列位于数组的什么位置并不重要(比如队列可能会一部分位于数组末端,一部分位于数组起始处,在内存视角上看不是连续的,但在逻辑上依然是连续的)。

     下面我们看相关的代码:

Code_层次遍历
/* 用队列(queue)来层次遍历一颗普通树*/
#define M 10 /*树的次/度:最大子节点数*/
#define QN 100 /*队列最大长度*/
typedef struct tnode
{
    char data;
    struct tnode *child[M];
}TNODE;

int lev_order(TNODE *t, int m)
{
    TNODE *q[QN];/* queue */
    TNODE *p;
    int head=0, tail=0, i;/*队列的头,尾索引*/
    q[tail++]=t; /*根节点入列*/
    while(head!=tail)/* 当队列不为空(首尾相遇时为空) */
    {
        p=q[head];/*从队首出列一个节点*/
        head=(head+1)%QN; /* head向后(队尾方向)移动一格 */
        printf("%c ",t->data);/*打印该节点*/
        
        for(i=0;i<m;i++) /* 出列节点的所有子节点按顺序入列(进入队列尾部) */
        {
            if(p->child[i])
            {
                if((tail+1)%QN==head) /*如果队列已经满了,返回0表示失败 */
                    return 0;
                q[tail]=p->child[i]; /*子节点入列*/
                tail=(tail+1)%QN;    /* tail向后移动一格 */
            }
        }
    }
    return 1; /*遍历结束,返回1表示成功*/
}

(2)关于树的少许补充:
2.1 二叉查找树和堆的结构看起来一样,但是区别是,二叉查找树:左子<=根<=右子; 而堆是:子节点<=根。另外,堆在逻辑上是树形,但存储是用数组存储的(节点按层次连续存入数组)。
2.2 中序遍历二叉查找树,输出的一个有序结果。
2.3 用作图法按前序,后序遍历树的规律:
      假设我们把二叉树的一个节点提取出来,我们看出,从节点中心垂直向上定义为0度角,逆时针正向,则节点的左右子树分支分别为120度(左子树)和240(右子树)。则我们可以用作图法得到各种序遍历的节点输出角度:从根节点上方向左下方画一条线外围住所有节点直到返回根节点,当线条经历到节点的以下角度时输出该节点(如图3所示,前序输出为1,2,4,5,3,6):
      前序输出角度:60度;
      中序(仅针对二叉树):180度;
      后序:300度。

       

 

(三)图的遍历

       图是一种复杂数据结构,图需要存储节点与节点之间的关联关系,主要有矩阵和链表两种存储方法。这里采用链表,针对每个节点都存储一个链表,链表中表示的是所有和该节点有链接的其他节点号。因此我们需要一个指针数组来存储所有链表。代码如下:

Code_图遍历
/*深度遍历和广度遍历图,图采用一组链表存储*/
#include <stdio.h>

#define N 30 /*最大节点数*/

typedef struct node
{
    int vno;
    struct node *next;
} edgeNode;

typedef edgeNode* Graph[N];

/*建立一张图。每输入一条边,与之联系的节点插入到该节点链表的头部*/
int create_list(Graph g)
{
    int vn, en, k, i, j;
    edgeNode *p;
    printf("\ninput count of nodes:\n");
    scanf("%d", &vn);
    printf("input count of edges:\n");
    scanf("%d",&en);

    for(k=0; k<N; k++)
        g[k]=NULL;
    for(k=0; k<en; k++)
    {
        printf("input the edge[%d](i,j):\n", k);
        scanf("%d,%d",&i,&j);

        p=(edgeNode*)malloc(sizeof(edgeNode));
        p->vno=j;
        p->next = g[i];
        g[i]=p;

        p=(edgeNode*)malloc(sizeof(edgeNode));
        p->vno=i;
        p->next = g[j];
        g[j]=p;
    }
    /*返回图中节点数*/
    return vn;
}

/*打印输出所有节点链表*/
void print_list(Graph g)
{
    int i;
    edgeNode *p=NULL;
    printf("\nGraph g:\n");
    for(i=0; i<N; i++)
    {
        if(g[i]==NULL)
            continue;
        printf("g[%d]: ",i);
        p=g[i]; /* p = head*/
        while(p!=NULL)
        {
            printf("%d - ",p->vno);
            p = p->next;
        }
        printf("NULL\n");
    }
}

/*------------------------递归,深度遍历-------------------------*/
/*存储节点状态,是否被遍历*/
int visited[N];

void clearflags()
{
    int i;
    for(i=0; i<N; i++)
        visited[i]=0;
}

/*深度优先遍历,采用递归函数*/
void dfs(Graph g, int i)
{
    edgeNode *t;
    printf("%4d,", i);
    visited[i] = 1;
    t=g[i];
    while(t!=NULL)
    {
        if(visited[t->vno]==0)
            dfs(g,t->vno);
        t=t->next;
    }
}

/*------------------------队列,广度遍历------------------------*/

#define M (2*N)

int queue[M];

/*广度优先遍历*/
void bfs(Graph g, int s)
{
    int i, v, w, head, tail;
    edgeNode *t;

    head=tail=0;

    /*访问该节点,并且该节点进队*/
    printf("%4d,", s);
    visited[s]=1;
    queue[tail++]=s;

    while(head != tail)         /*当队列不为空时*/
    {
        v = queue[head];            /*队列首节点出列*/
        head = (head+1)%M;         /*head指针向右方移动*/
        for(t=g[v]; t!=NULL; t=t->next)
        {
            w = t->vno;
            if(visited[w]==0)
            {
                printf("%4d,", w);
                visited[w] = 1;
                if((tail+1)%M == head)
                {
                    printf("queue's size is too small! !");
                    return;
                }
                queue[tail] = w;    /*顶点w进队*/
                tail = (tail+1)%M;
            }
        }
    }
}

/*
   0---1
   |\ /|
   | 4 |
   |/ \|
   3---2   
 */
void main()
{
    Graph g;
    int i,vn;
    vn = create_list(g);
    printf("g has %d nodes\n",vn);

    /*深度遍历*/
    printf("\n dfs: ");
    clearflags();
    dfs(g,0);

    /*广度遍历*/
    printf("\n bfs: ");
    clearflags();
    bfs(g,0);

    print_list(g);
    getch();
}

 

       针对下面这张图:

       

       程序的输出如下:

 dfs:    0,   4,   3,   2,   1,    (深度优先遍历)
 bfs:    0,   4,   3,   1,   2,    (广度优先遍历)

 

Graph g: (图g的所有节点链表)

g[0]: 4 - 3 - 1 - NULL
g[1]: 4 - 2 - 0 - NULL
g[2]: 4 - 3 - 1 - NULL
g[3]: 4 - 0 - 2 - NULL
g[4]: 3 - 2 - 1 - 0 - NULL

时间: 2024-10-23 20:11:08

[非原创]树和图的遍历的相关文章

树的遍历与图的遍历

研发时候,不要受原来的术语的影响,其实就是想着原来学过的或者看过的可以解决新遇到的问题,这其实是侥幸心理,忘记原来的术语吧,那只是你创新的源泉. 遍历就是把节点按一定规则构成一个线性序列,不同的规则得到不同顺序的线性序列,仅此而已 . 算法是实际问题工作步骤的抽象,不要一味想算法,想想实际情况怎么做的,然后提取算法,然后优化. 不论怎样,要和具体的数据结构结合在一起. 一.树的遍历 对于树的遍历,有三种,就拿前序遍历来说,得到的序列不论怎么拆分(子串,就是要连续),始 终要是根左右,跟在左右前面

python 回溯法 子集树模板 系列 —— 8、图的遍历

问题 一个图: A --> B A --> C B --> C B --> D B --> E C --> A C --> D D --> C E --> F F --> C F --> D 从图中的一个节点E出发,不重复地经过所有其它节点后,回到出发节点E,称为一条路径.请找出所有可能的路径. 分析 将这个图可视化如下: 本问题涉及到图,那首先要考虑图用那种存储结构表示.邻接矩阵.邻接表....都不太熟. 百度一下,在这里发现了一个最爱.

图的遍历(搜索)算法(深度优先算法DFS和广度优先算法BFS)

图的遍历的定义: 从图的某个顶点出发访问遍图中所有顶点,且每个顶点仅被访问一次.(连通图与非连通图) 深度优先遍历(DFS): 1.访问指定的起始顶点: 2.若当前访问的顶点的邻接顶点有未被访问的,则任选一个访问之:反之,退回到最近访问过的顶点:直到与起始顶点相通的全部顶点都访问完毕: 3.若此时图中尚有顶点未被访问,则再选其中一个顶点作为起始顶点并访问之,转 2: 反之,遍历结束.  连通图的深度优先遍历类似于树的先根遍历 如何判别V的邻接点是否被访问? 解决办法:为每个顶点设立一个"访问标志

图的遍历之深度优先搜索和广度优先搜索

深度优先搜索的图文介绍 1. 深度优先搜索介绍 图的深度优先搜索(Depth First Search),和树的先序遍历比较类似. 它的思想:假设初始状态是图中所有顶点均未被访问,则从某个顶点v出发,首先访问该顶点,然后依次从它的各个未被访问的邻接点出发深度优先搜索遍历图,直至图中所有和v有路径相通的顶点都被访问到.  若此时尚有其他顶点未被访问到,则另选一个未被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止. 显然,深度优先搜索是一个递归的过程. 2. 深度优先搜索图解 2.

编程c语言-c语言版的数据结构中求图的遍历

问题描述 c语言版的数据结构中求图的遍历 调试时为什么会出现已停止工作??具体情况是出现了一个问题,导致程序停止正常工作,如果有可用的解决方案,Windiws将关闭程序并通知你 解决方案 贴出你的代码.代码是调试才能发现错误的.哪有看代码看出错误的. 你自己也要学会调试. 解决方案二: 数据结构(C语言版)规范代码之图(邻接多重表遍历)数据结构(C语言版)摘录--树和二叉树数据结构(C语言版)摘录--图 解决方案三: 看着真费劲.有malloc申请内存,没看到有free呢.

图的遍历-(深度优先&amp;amp;广度优先)

1.调用代码入口: using System; namespace 图_图的遍历 { internal class Program { private static void Main(string[] args) { var a = new AdjacencyList<char>(); Console.WriteLine("1.初始化树结构:"); Console.WriteLine("================================="

systreeview32-跨进程控制SysTreeView32树状图控件的难题

问题描述 跨进程控制SysTreeView32树状图控件的难题 最近公司在做一个智能化股票交易的项目,需要控制股票交易软件树状图进行翻页,刚开始我使用普通的WM_LBUTTONDOWN和WM_LBUTTONUP消息进行发送,发现只是实现了选择树状图节点,而没有达到实际效果,也就是控制页面跳转,遂怀疑是WM_NCHITTEST的问题,可是加入了WM_NCHITTEST消息,并把截获的消息全部依次发送后,仍无法成功.后来发现在WM_LBUTTONUP消息后,有一个关键的TVM_HITTEST我没有进

树与森林的遍历

一.树的遍历      1.先根(次序)遍历树           先访问树的根节点,然后依次先根遍历根的每棵子树      2.后根(次序)遍历           先依次后根遍历每棵子树,然后访问根结点. 上面的先根遍历为:A B C D E 上面的后根遍历为:B D C E A 二.森林的遍历      1.先序遍历森林           若森林非空,则可按照下述规则遍历之:                (1)访问森林中第一棵树的根节点                (2)先序遍历第一

c-先序建的树,中序遍历,为啥在Push处会中断运行啊

问题描述 先序建的树,中序遍历,为啥在Push处会中断运行啊 #include #include #include #define S_SIZE 100 #define STACKINCREMENT 10 typedef struct BiTNode { char data; struct BiTNode *lchild, *rchild; }BiTNode,*BiTree; typedef BiTNode ElemType; typedef struct { ElemType *base; E