| 网站首页 | 业界新闻 | 小组 | 威客 | 人才 | 下载频道 | 博客 | 代码贴 | 在线编程 | 编程论坛
欢迎加入我们,一同切磋技术
用户名:   
 
密 码:  
共有 7782 人关注过本帖, 1 人收藏
标题:天,没想到图的深度优先遍历的非递归算法这么难写...
只看楼主 加入收藏
取消关键字高亮
geninsf009
Rank: 3Rank: 3
等 级:论坛游民
威 望:8
帖 子:613
专家分:95
注 册:2008-8-16
结帖率:100%
收藏(1)
 问题点数:0 回复次数:14 
天,没想到图的深度优先遍历的非递归算法这么难写...
写过图的深度优先遍历的递归算法,觉得很简单,就像树的递归遍历一样,

也曾经写过孩子-兄弟链表法的树的非递归遍历,本以为这个能写了,写图的深度优先遍历非递归算法也没什么,

可我想错了,很多的问题是树中没有的,在这里我列举一下:
 
(1)树的非递归遍历中一般需要把当前访问的结点的nextSibling压栈,而在图中要想获得下个邻接结点
还必须知道当前结点的父结点,所以要连同父结点一起压栈,这样,我的程序里,对堆栈的操作都是一压
两个,一弹两个,分别是当前结点和它的父结点;

(2)往往有时候从堆栈探出的结点已经访问过了,这种情况在树的遍历中是不可能出现的,但在图中是有可能的,
因为图中可能存在回路,所以往往祖先结点的兄弟,也有可能是子孙结点的儿子,呵呵.所以对弹出的元素还得检测
一下是否已经访问,如果已经访问,还得继续弹出(而且一弹就是两个),直到弹出的是没有访问过的;也许有人会
问那既然已经访问过了,为什么当初还把它压栈呢?其实当初作为祖先的兄弟压栈的时候没有访问过,但后来以子孙
的儿子的身份被访问了,呵呵,所以在弹出堆栈的时候还得再次检测是否已经访问,这也就是很多教材里讲的深度
优先生成树里的回边问题了;

(3)判断遍历结束的条件:当前结点如果已经没有未访问的邻接结点了,而且堆栈已经空了也不好回溯了,这个时候
遍历就可以结束了,也就是走投无路的时候就是结束的时候,当然如果用for(int i=0;i<numVerice;i++)语句的话时间
复杂度也是一样的,只是要多一个结点个数的信息,而且不利于非连同图的遍历;

PS:我写的程序是基于邻接表的,这个算法很多的教材上并没有,花了三天的时间才想出来,调试成功了,可是觉得
太复杂了...大家如果有更好的非递归的思想,在下洗耳恭听...
另外程序中的getFirstNeighbor(p)函数的作用是获取p的第一个邻接结点,getNextNeighbor(p,w)函数的作用是获取,
p的邻接结点w后的下一个邻接结点,想必读过相关教材的话,这两个函数就不过多介绍了...

/////////////////////////////////////////////////
//DepthFirst()公有成员函数
//采用非递归的方式深度遍历当前图
/////////////////////////////////////////////////
template<class T,class E>
void Graphlink<T,E>::DepthFirst(int start)
{
    visited=new bool[numVertices];   //是否被访问过的标记
    for(int i=0;i<numVertices;i++)
        visited[i]=false;

    LinkedStack<int> Stack;          //回溯过程中用到的堆栈
    int p=start;                     //指向当前的结点
    int parent=-1;                   //指向当前结点的遍历父结点
    int first;                       //存放获取长子结点号
    int next;                        //存放下个兄弟结点的地址
    do
    {
        cout<<getValue(p)<<" ";                //访问当前结点p
        visited[p]=true;                       //设置已经访问过

        first=getFirstNeighbor(p);             //获取当前访问结点p的未访问过的第一个邻接结点号
        while(first!=-1 && visited[first]==true)
            first=getNextNeighbor(p,first);    //继续看下个邻接结点
 
        if(first!=-1)                          //如果不是根结点而且长子结点不空
        {
            if(p==start)                       //如果当前访问的结点是根结点
                next=-1;                       //则没有下个兄弟结点
            else
            {
                next=getNextNeighbor(parent,p);//寻找p之后第一个未被访问的第一个兄弟结点
                while(next!=-1 && visited[next]==true)
                    next=getNextNeighbor(parent,next);
            };
            
            if(next!=-1)                       //如果p的下个兄弟结点存在
            {
                Stack.Push(parent);            //先把父结点压入堆栈
                Stack.Push(next);              //再把该下个兄弟结点压入堆栈
            };
            parent=p;
            p=first;                           //下个结点
        }
        else                                   //如果长子结点是空的
        {
            if(p==start)                       //如果就是根结点
                break;                         //推出遍历循环
            else
            {
                do
                {
                    Stack.Pop(p);              //从堆栈中弹出一个结点作为下个结点
                    Stack.Pop(parent);         //从堆栈中同时获得
                }
                while(visited[p]!=false);      //弹出的结点如果是已经访问过的,就继续弹
            };
        };
    }while(first!=-1 || !Stack.IsEmpty());     //如果堆栈和长子结点都空了,说明遍历结束
};
/////////////////////////DepthFirst()公有函数结束

[[it] 本帖最后由 geninsf009 于 2008-9-26 18:45 编辑 [/it]]
搜索更多相关主题的帖子: 遍历 递归 算法 深度 
2008-09-26 14:46
geninsf009
Rank: 3Rank: 3
等 级:论坛游民
威 望:8
帖 子:613
专家分:95
注 册:2008-8-16
收藏
得分:0 
大家讨论一下呢,觉得图的编程的趣味性比树更多些,

以下是前几天写求图的深度优先生成树的递归算法,返回的是

树根,树采用孩子-兄弟链表法表示:

/////////////////////////////////////////////////
//DFSTree()公有成员函数
//递归:得到当前图的深度优先生成树的算法
//其中该树采用FirstChild-NextSibling存储结束
//v是遍历起始的顶点,subTree是以v为当前顶点的生成子树
/////////////////////////////////////////////////
template<class T,class E>
TreeNode<T>* Graphlink<T,E>::DFSTree(int v)
{
    T value=getValue(v);                 //通过结点v创建一个树的结点
    TreeNode<T>* subTree;
    subTree=new TreeNode<T>(value);      //作为当前生成子树的根结
    visited[v]=true;                     //当前结点v已经访问过

    int flag=1;                          //判断是否是长子结点
    TreeNode<T>* ptr=NULL;               //获取长子树的树根
    TreeNode<T>* pre=NULL;               //保留前棵兄弟子树的根结点

    for(int p=getFirstNeighbor(v);       //递归遍历v的所有未访问的邻接结点
        p!=-1;p=getNextNeighbor(v,p))
    {                                    
        if(visited[p]==false)            //如果邻接点p还未被访问过
        {
            ptr=DFSTree(p);              //递归生成一棵子树
            if(flag==1)                  //如果是长子结点
            {
                subTree->firstChild=ptr;
                flag=0;                  //标志表示已经不是长子结点
            }
            else
                pre->nextSibling=ptr;    //接到前面的子树的后面
            pre=ptr;
        };
    };
    return subTree;                      //返回根结点指针
};
////////////////////////////////DFSTree()函数结束
2008-09-26 22:32
geninsf009
Rank: 3Rank: 3
等 级:论坛游民
威 望:8
帖 子:613
专家分:95
注 册:2008-8-16
收藏
得分:0 
补上今天刚写完,并通过运行的最下生成树的类的实现,觉得数据结构很多的算法都是比划着简单,实现难啊...
共享一下我刚通过运行的程序,大家参考:
#ifndef MINSPANHEAP_H
#define MINSPANHEAP_H

#include<iostream.h>
#include<stdlib.h>
#include"Graphlink.h"
#include"Graphmtx.h"
#include"MinHeap.h"
#include"UFSet.h"

const float maxValue=10000;
const int defaultsize=30;

//////////////////////////////////////////////////
//MSTEdgeNode结构 最小生成树边结点的声明
//本最小生成树采用边的集合进行表示
//////////////////////////////////////////////////
template<class T,class E>
struct MSTEdgeNode
{
    int head;          //边的一个端点
    int tail;          //边的另一个端点
    E weight;          //当前边权值
    MSTEdgeNode()      //默认构造函数
    {head=-1;tail=-1;weight=0;};
    
    /*以下重载关系运算符*/
    bool operator>(MSTEdgeNode<T,E>& Node)
    {return weight>Node.weight;};
    bool operator>=(MSTEdgeNode<T,E>& Node)
    {return weight>=Node.weight;};
    bool operator<(MSTEdgeNode<T,E>& Node)
    {return weight<Node.weight;};
    bool operator<=(MSTEdgeNode<T,E>& Node)
    {return weight<=Node.weight;};
    bool operator==(MSTEdgeNode<T,E>& Node)
    {return weight==Node.weight;};
};
///////////////////////////////MSTEdgeNode结构结束

//////////////////////////////////////////////////
//MinSpanTree类模  最小生成树的实现
//T是顶点数据域的类型,E是边的权值的数据类型
//////////////////////////////////////////////////
template<class T,class E>
class MinSpanTree
{
protected:
    MSTEdgeNode<T,E>* EdgeValue;        //边集合数组
    int maxSize;                        //边集合数组
    int currentSize;                    //当前数组的长度
public:
    MinSpanTree(int sz=defaultsize)     //构造函数
    {
        maxSize=sz;currentSize=0;       //初始化数据成员
        EdgeValue=new MSTEdgeNode<T,E>[maxSize];
        if(EdgeValue==NULL)
        {
            cout<<"内存分配失败!"<<endl;
            exit(1);
        };
    };                             
    ~MinSpanTree()                      //析构函数
    {delete [] EdgeValue;};
    bool Insert(MSTEdgeNode<T,E>& Node);//把一条边插入到当前的生成树中
    void Cruskal(Graph<T,E>& G);        //Cruskal算法:通过图G来构造当前的最小生成树
    void Prim(Graph<T,E>& G,int start); //Prim算法:通过图G来构造当前的最小生成树

    //友元重载<<输出运算符输出当前的最小生成树
    friend ostream& operator<<(ostream& os,MinSpanTree<T,E>& MST);
};
/////////////////////////////////MinSpanTree类模板

//////////////////////////////////////////////////
//Insert()公有成员函数
//在当前的最小生成树中插入一条边,插入成功返回true
//////////////////////////////////////////////////
template<class T,class E>
bool MinSpanTree<T,E>::Insert(MSTEdgeNode<T,E>& Node)
{
    if(currentSize==maxSize)
    {
        cout<<endl
            <<"当前的最小生成树的空间已经满了!"
            <<endl;
        return false;
    };
    //把边插入其中
    EdgeValue[currentSize]=Node;
    currentSize++;
    return true;
};
//////////////////////////Insert()公有成员函数结束

//////////////////////////////////////////////////
//Cruskal()公有成员函数
//利用克鲁斯卡尔算法通过当前图来构造一棵最小生成树
//即所谓的贪心算法,每次都取剩下的最大或者最小,但
//该边两个的顶点不能在同一连通分量中,反复迭代
//参数采用的是图的基类Graph<T,E>,在具体运行的
//时候可以实现基类动态向子类的类型转换
//////////////////////////////////////////////////
template<class T,class E>
void MinSpanTree<T,E>::Cruskal(Graph<T,E>& G)
{
    /*首先把所有的边按照权值放入最小堆中*/
    int numEdges=G.NumberOfEdges();            //得到图G的边的条数
    MinHeap<int,MSTEdgeNode<T,E> >MH(numEdges);//依照图的边数定义最小堆
    MSTEdgeNode<T,E> Edge;                     //边结点变量
    for(int u=0;u<numEdges;u++)            //无重复地遍历所有的边
    {
        for(int v=u+1;v<numEdges;v++)      //把边压入最小堆
        {
            int cost=G.getWeight(u,v);     //获取边(u,v)的权值
            if(cost>0 && cost<maxWeight)   //如果该边存在且有效
            {
                Edge.head=u;               //初始化边结点内容
                Edge.tail=v;
                Edge.weight=cost;     
                MH.Insert(Edge);           //把该边加入到最小堆中
            };
        };
    };

    /*从对中取出不在同一连通分量中的最小边插入堆中的迭代过程*/
    int numVertices=G.NumberOfVertices();  //获得图中的当前顶点的个数
    UFSet UF(numVertices);                 //用于判断是否在同一个连同分量的并查集
    int count=1;
    while(count<=numVertices-1)            //从堆中逐个取出权值最小的边
    {
        MH.RemoveMin(Edge);                //从最小堆中取出一条最小边

        u=UF.findRoot(Edge.head);          //找u所在集合的根结点
        int v=UF.findRoot(Edge.tail);      //找v所在集合的根结点

        if(u!=v)                           //如果Edge的两个端点不在一个连通分量
        {
            Insert(Edge);                  //把该边插入到最小生成树中去
            count++;
            UF.Union(u,v);                 //把u,v两个集合合并,连接两个分裂的连通分量
        };
    };
};
/////////////////////////////Cruskal()公有函数结束

//////////////////////////////////////////////////
//Prim()公有成员函数
//通过Prim算法实现把图G得到相应的最小生成树
//每次从某个结点出发,把与最小生成树中结点相关联的
//权值最小的边找出来,并加入最小生成树,反复迭代
//其中start是起始结点
//////////////////////////////////////////////////
template<class T,class E>
void MinSpanTree<T,E>::Prim(Graph<T,E>& G,int start)
{
    int numVertices=G.NumberOfVertices(); //得到图当前所有顶点数
    bool* vmst=new bool[numVertices];     //用于标记已经加入生成树的结点
    for(int i=0;i<numVertices;i++)        //初始化为未加入
        vmst[i]=false;
    vmst[start]=true;                     //先把start加入最小生成树
    MinHeap<int,MSTEdgeNode<T,E> >
        MH(G.NumberOfEdges());            //迭代过程中用于求权值最小的边的堆栈
    MSTEdgeNode<T,E> Edge;                //一个最小生成树的结点变量
    int p=start;                          //新加入最小生成树的那个顶点
    int count=1;                          //迭代计数器,一共要迭代n-1次
    
    do
    {
        /*把新结点加入生成树的顶点集合中*/
        vmst[p]=true;
        /*把与新结点相关的并且另一个顶点不
        在生成树中的边加入最小堆*/
        int n=G.getFirstNeighbor(p);      //先获取结点p的第一个邻接
        while(n!=-1)                      //遍历所有的邻接结点
        {
            if(vmst[n]!=true)             //如果邻接点n不在生成树中
            {                             //把该边加入最小堆
                Edge.head=p;              //生成树内的结点
                Edge.tail=n;              //生成树外的结点
                Edge.weight=G.getWeight(p,n);
                MH.Insert(Edge);          //把该边加入最小堆
            };
            n=G.getNextNeighbor(p,n);     //获取下个邻接结点
        };

        /*从堆中取出权值最小的边加入到生成树中去*/
        do
        {
            MH.RemoveMin(Edge);           //取出最小边
            if(vmst[Edge.tail]!=true)     //如果另一个结点不在生成树中
            {
                Insert(Edge);             //把该边插入生成树
                count++;                  //加入边的条数的计数器
                break;
            };
        }
        while(!MH.IsEmpty());
        p=Edge.tail;
    }
    while(count<=numVertices-1);
};
////////////////////////////Prim()公有成员函数结束

//////////////////////////////////////////////////
//友元重载<<输出运算符输出当前的最小生成树
//////////////////////////////////////////////////
template<class T,class E>
ostream& operator<<(ostream& os,MinSpanTree<T,E>& MST)
{
    cout<<"当前的最小生成树的边集合是:"<<endl;
    for(int i=0;i<MST.currentSize;i++)
    {
        cout<<"("<<MST.EdgeValue[i].head         //输出边的一端
            <<","<<MST.EdgeValue[i].tail         //输出边的另一端
            <<","<<MST.EdgeValue[i].weight
            <<")"<<endl;                         //输出该边的权值
    };
    return os;
};
////////////////////////////////////<<友元重载结束
2008-10-05 17:03
blueboy82006
Rank: 5Rank: 5
来 自:幻想世界
等 级:贵宾
威 望:16
帖 子:1227
专家分:57
注 册:2007-7-23
收藏
得分:0 
得慢慢看...

2008-10-05 17:39
很远的那颗星
Rank: 2
等 级:新手上路
威 望:4
帖 子:544
专家分:0
注 册:2008-6-30
收藏
得分:0 
如果是我写,肯定用递归.原因:
递归算法清晰易懂,短小精悍..

当然,不是所有以能用递归的程序我都用递归..

Fighting~~~~~~~~
2008-10-05 17:43
blueboy82006
Rank: 5Rank: 5
来 自:幻想世界
等 级:贵宾
威 望:16
帖 子:1227
专家分:57
注 册:2007-7-23
收藏
得分:0 
回复 5# 很远的那颗星 的帖子
说实话,递归真伤脑细胞...
不过,太麻烦的,不用它,就伤神...

2008-10-05 17:51
很远的那颗星
Rank: 2
等 级:新手上路
威 望:4
帖 子:544
专家分:0
注 册:2008-6-30
收藏
得分:0 
[bo][un]blueboy82006[/un] 在 2008-10-5 17:51 的发言:[/bo]

说实话,递归真伤脑细胞...
不过,太麻烦的,不用它,就伤神...

呵呵..没错..

楼主还行吗嘛...写了两个最小生成树,不过不知说些啥好.Cruskal跟Prim都是贪心..好像单是写这样的代码还比较简单
关键是知道怎样用才行.

Fighting~~~~~~~~
2008-10-05 17:59
geninsf009
Rank: 3Rank: 3
等 级:论坛游民
威 望:8
帖 子:613
专家分:95
注 册:2008-8-16
收藏
得分:0 
楼上的大侠说的是啊,在下涉世不深,还有很多的代码没写过呢...
2008-10-05 18:23
很远的那颗星
Rank: 2
等 级:新手上路
威 望:4
帖 子:544
专家分:0
注 册:2008-6-30
收藏
得分:0 
[bo][un]geninsf009[/un] 在 2008-10-5 18:23 的发言:[/bo]

楼上的大侠说的是啊,在下涉世不深,还有很多的代码没写过呢...


啦啦啦...我也涉世不深,菜鸟一个.以后多多共同讨论,一起进步..

Fighting~~~~~~~~
2008-10-05 18:38
geninsf009
Rank: 3Rank: 3
等 级:论坛游民
威 望:8
帖 子:613
专家分:95
注 册:2008-8-16
收藏
得分:0 
大家共勉..
2008-10-06 16:05
快速回复:天,没想到图的深度优先遍历的非递归算法这么难写...
数据加载中...
 
   



关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.041798 second(s), 7 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved