Dragon 第一期开发记录(第二阶段 图)

2.2

 

今晚开始写图部分的算法。但是线性表留下的问题一定要解决:

 

(1)模板类的const修饰符的重载? 问题出现在SequenceList等的复制构造函数处。


(2)Select函数中没有delete mlist;


(3)模板类的虚析构函数没有解决。当然包括虚构造函数。


 (4)  基础设施类 日志类 报错类没有做


(5)heapsort中 MaintaiHeap中,对空的标识是使用-1,没有考虑泛型设计

 

----------------------------------------------------------------------------------------------------------------

2.2

今晚的目标是完成堆的数据结构。。。。

 

问题:

1. 因为要将其写成一个动态数据结构,则需要考虑插入 删除节点 于是就需要使用动态表 或者数的结构。 不能直接使用SequenceList

--

2. 插入节点最少要时间是多少?

-- 答:其实很简单就是插到最后面,然后通过迭代向上交换即可。所以是Log(n)。

 

基本上完成,但是问题1没有解决,而且优先队列还没有写完。虽然不多。

看来进度是严重落后呀。。。。编码速度太慢了!!!!

 

---------------------------------------------------------------------------------------------------------------------

2.3

今晚完成了优先队列 但是还没有测试

进度落后 而且应该要集中精力提高编码的效率 分心太严重

 

 

-----------------------------------------------------------------------------------------------------------------------------

2.4

今晚完成优先队列的测试

Errors:

(1)类方法中不能这样写:    virtual LinearList<T>* ToLinearList() =0 const ; 要把=0删去

(2)在C++中,注意不能这样定义一些类中的对象:Heap<T> m_pHeap; 特别是Heap是一个虚类时会报错。因为这样写不是表示声明,而是直接调用构造函数定义了。所以要使用对象的时候用指针吧。

(3)const T 不能转换为T, T转换为const T可以。

(4)枚举不能跟整型自由地(隐式地转换)只能由整型显式地转换为枚举型。

(5)编译的时候一定是先声明类型,然后定义变量,一定是按顺序进行的,比较不够智能化。

(6)枚举类型不能由整型变量初始化,只能由整型常量表达式来初始化。所以(4)是错的。

        这个的解决方案是使用一个namespace,将枚举和静态常量封装进去,在一个模板类里面定义枚举,在外部无法使用(指的是无法使用枚举中的常量)

(7)定义虚函数如果不写virtual func() = 0, 就会出:外部链接错误,无法找到nresolved external symbol "public: virtual void __thiscall Heap<int>::Modify(int,int)" (?Modify@?$Heap@H@@UAEXHH@Z),那么应该说前天的虚析构函数也能解决了。

(8)运行中出现的问题:向优先队列入队时没有使堆结构发生变化。原来堆的设计中还存在问题,那就是没有考虑向上作堆性质维护的情况。

比如:在最大堆里面,修改其中一个结点的关键字,使之变大,之后激发的操作应该是向上维护堆性质。而目前的MaintainHeap都是向下维护,而算法书上的优先队列提供的算法的情况是:最小优先队列--IncreaseKey---只能increase!!!

 

 

------------------------------------------------------------------------------------------------------------

2.5

今晚开始写图部分 首先 进行图数据结构的定义,在一月初写的数据结构的基础上进行改写。

 

问题:

(1)经常忘记一个问题。error C2501: 'GraphAdjList' : missing storage-class or type specifiers 这种错误表示找不到定义。

(2)问题很多。记下一个,模板类的友元函数声明:

C++PL里面的说法是:friend Vector operator*<>(...) 改后的确识别出是友元,但是仍然有许多错。

(3)模板类的声明:template<class T>class Matrix! 注意!!

(4)还没成功,模板类要注意的细节太多了。。。。。

 

---------------------------------------------------------------------------------------------------------------------------------------------

2.6

(1)解决昨晚关于模板类的模板友元函数的问题了:C++ PL中的写法居然是有问题的!!!!!!! 看来尽信书不如无书这句话是太对了。而且不能省钱了 C++ Primer那几本书一定要买了。正确的写法是:

 template <typename E>friend void PrintGraph(GraphAdjMatrix<E> &graph);

   可以参考这里:

    http://www.diybl.com/course/3_program/c++/cppjs/20090922/176459.html

 

* 有一个问题是,太疏忽了:构造函数忘记写<E>!!!!

 

(2)接下来剩下的问题是:无法识别出类型参数的问题。。。。。

终于他娘的弄出来了!!!! 这错误我都不知道怎么总结好。

首先:namespce的问题? 报错显示是无法识别类型参数,但是按道理,这根本不可能识别不出来,跟容器类是一模一样的。仔细观察报错的提示信息,发现里面的形参跟 原本的形参略有不同:

 error C2784: 'void __cdecl g::PrintGraph(class g::GraphAdjList<E> &)' : could not deduce template argument for 'class g::GraphAdjList<E> & ' from 'class GraphAdjList<int>'

注意上面多了一个g::

这怎么回事呢?我不过就是将两个全局函数放到了命名空间g里面了吗?这样调用的时候自然是需要加上g::的。像这样:

g::PrintGraph(graph);

但是graph不可能是g::GraphAdjList<E> &的 我怎么看我定义的头文件里面,两个模板类都是位于g之外的。看来命名空间这里有玄机。

---我知道问题出在哪里了。因为我在命名空间g中声明了两个模板类。。。还不是为了模板函数的形参。。。这样编译器就认为我是在g中定义的模板类了。。。。。。

 

*另外要注意一点是也是命名空间问题,对于cout,istream这样的流对象,一定注意要加上std,不然错了都不知道怎么死的。

 

(3) 完成了队列,使用了双链表,对链表进行了修改。

(4)可以去写深搜和广搜了。。。。。。。。。。。。。。。

 

---------------------------------------------------------------------------------------------------------------------------------------------

2.7

 

1. 写完广搜

2. 遗留问题:迭代函数子Op没有定义, 另外一个是PrintArray模板函数对int的偏特化。

 

-------------------------------------------------------------------------------------------------------------------------------------------

2.8

 

今晚完成了深搜和拓扑排序。。。基本上是写了两遍深搜,没出现大的问题。主要因为难度太低。

 

--------------------------------------------------------------------------------------------------------------------------------------------

2.9

今晚仅仅完成了转置。这两晚偷懒了。明后两晚要提速了。

 

-------------------------------------------------------------------------------------------------------------------------------------------

2.10

今晚完成了强连通分支,

        1. 利用拓扑排序获得finishTime倒序的节点编号

        2. 对graph进行转置

        3. 对转置后的graph进行深搜,每次返回一个list

 

今晚对之前的动态表进行测试修改:

问题:

(1)Insert方法出现问题,当插入需要扩张后,未能获得原来的句柄。

        解决:其实原来的方法是错误的!!! 不需要构造一个LinerList。。。只需要构造一个T* 划出一个新的连续内存区域即可!!!

(2)强连通分支ms有问题!!!

 

-------------------------------------------------------------------------------------------------------------------------------------------------

2.11

(1)强连通分支问题解决:

问题出现在:发生在用转置后得到的拓扑排序链表进行深搜时,不是将color[(*pList)[i]]=Black,而是color[i]=Black

遇到问题一定要冷静 冷静 再冷静 慢下来 慢下来 慢下来

 

(2)对深搜广搜的泛型函数子 可以简单地加上functor(key),

事实上问题出现在: 如果传入0,则不做任何操作。 这个需求没有解决。

 

写Bellman-Ford算法。还没完全弄清楚为什么可以用dv>du+w(u,v)来测定图是无负权回路的

写完了BF算法,用了大约35分钟。。。这编码速度真的是。。。。

 

2.17

整整中断了5天呀。。。开始编码!

 

今晚写Dijkstra算法。问题:

1. 对优先队列的最小优先和最大优先的界面设置没有做好。

2. Dijkstra算法中的优先队列对distance[]进行,而问题在于对优先队列出队的元素,只是distant[i],如何O(1)时间内获得 i 呢?

这个问题拟用查找解决,但是现在看来还是有问题。

3. 第三个问题更为重要,就是在做Relax时,需要对优先队列中的某个关键字进行Decrease,但是如何定位这个关键字呢?

 

看来需要更好的方案才行。

 

-------------------------------------------------------------------------------------------------

2.18

没想到 但是找到了新的方案,那就是不用在优先队列删除节点的方法,而是用插入节点的方法,这样就避免了对队列中的关键字进行操作!!!!

有时候思维有放开一点。!!!

 

新的方法类似广度优先搜索,不同的是使用了优先队列而不是简单的队列!!!

 

可以使用自定义的类型 {id, key} 来定义优先队列。只需要简单地重载一个<运算符就可以了。编程思维还是比较死。。。

template <class E>struct T{

bool operator< (T t)

{

    return dis<t.dis;

}

int id;

E dis;

}

调试没通过。

 

------------------------------------------------------------------------------------------------------------------------------------

2.20

前天调试没通过的原因是模板类PriorityQueue这个模板类中的每一个Item作为模板类出现是不行的,错误例子:

 

 PriorityQueue<QueueItem<E>>* pQueue = new PriorityQueue<QueueItem<E>>(pq::PQ_MIN);

能够推导T<E>形式的是模板函数而不是模板类。真正的实现方法是:

(1)

template<class T, template<class>class C>class X{

C<T> m;

}

(2)

使用容器:

如stack实现:

template<class T,class C=deque<T>>class std::stack{}

 

而对于目前面临的情况:PriorityQueue模板类需要传入一个T<E>的模板参数,如今应用的方法是typedef QueueItem<E> _QueueItem

解决了问题。编译通过,但是仍然存在错误。

 

记录其中一个编译错误是:Heap::Insert中的代码:

//for heap::Insert()
        //    m_pHeap->Insert((T)0, m_iSize);
        //    Modify(m_iSize,element);
        //    T(0) need a constructor for a integer argument

注意红色处,需要将整型0转型为T,这里即为QueueItem<int>,否则报错。

解决方法是在QueueItem中添加参数为整型的构造函数

QueueItem(int x) {}

同时注意要添加空的默认构造函数

QueueItem(){}

否则在建立QueueItem数组时报错,因为没有默认构造函数了。

 

------------------------------------------------------------------------------------------------------------

2.22

继续Dijsktra算法。

软件工程不好做,原来非常简单的算法,由于多个组件的组合,使得大量的Bug产生。

 

1.错误:new一个PriorityQueue后,对象中m_pHeap的m_iSize=-84323234,未赋值成功。

这个错误发生在OHeap类中:

      在这个类中,有几个构造函数,为图方便,我是这样写的:

      OHeap<T>()
    {

        OHeap<T>(0, heap::MAXHEAP);
    }

    OHeap<T>(heap::HeapType type)
    {
        OHeap<T>(0,type);   

}

    OHeap<T>(int size, heap::HeapType type)
    {
        m_pHeap = new DynamicList<T>(size);
        m_iSize = size;
        m_eHeapType = type;

    }

    在使用PriorityQueue<_QueueItem>* pQueue = new PriorityQueue<_QueueItem>(pq::PQ_MIN);构建优先队列时,

    调用了OHeap<T>(heap::HeapType type)这个构造函数,结果发现在构造过程中明明将m_iSize设为0了(跟踪调试时观察到),但是跳出函数后,m_iSize仍然是未初始化的乱值,因为我猜测是构造函数出了问题,估计是构造函数中调用构造函数,会产生多个内存对象(???)

 

2. 第二个是个可笑的错误

    做Relax时,错将判断条件 dis[edge->toVex] > dis[p] + edge.weight 写成 dis[edge->toVex] < dis[p] + edge.weight

    究其原因,一方面是对算法不熟悉,另外 以后可写成 dis[p] +edge.weight < dis[edge->toVex],这样写出错的几率小点。

 

3. 第三个错误更可笑。漏写了edge = edge->next;

4. 第四个错误发生在Heap中的DownwardMaintainHeap()

    错误例子是:

        else if (m_eHeapType == heap::MINHEAP)
        {
            for (int j=i;j<=m_iSize-1; j*=2 )
            {
                int min = j;
                if ( 2*j+1 <= m_iSize-1)
                    if ( (*m_pHeap)[2*j+1] < (*m_pHeap)[j] ) min=2*j+1;
               
                if ( 2*j+2<=m_iSize-1)
                    if ( (*m_pHeap)[2*j+2] < (*m_pHeap)[min]) min=2*j+2;
               
                if (j==min) util::Swap( (*m_pHeap)[j],(*m_pHeap)[min]);
                j=min;

            }
        }

        这里的逻辑根本就是错误的。j=min已经让j向下移动了,怎么可能还要j*=2呢?

 

*另外还有一个错误是:使用的测试数据是Adjlist_test_input.txt,这个图是有负权值的,Dijkstra不能解决有负权边的问题。

========================

到此,解决了这个算法。

 

-----------------------------------------------------------------------------------------------------------------------------------------------

2.23

今晚写每对顶点间最短路径的第一个:矩阵乘法算法,是一种最原始的动态规划的想法

其中花了约一节的时间思考,如果不是用临时矩阵存储下一阶的矩阵,直接在原来的矩阵上运算是否会有问题,目前的考虑是应该不会出错,起码最终结果不会出错,因为每次更新都是在最短路径的基础上进行更新,而最短路径具有下界收敛。

 

但是编码效率还是偏低了,没有调出来,最后的问题是 graph.m_pMatrix里面的元素,不连通两结点间距离表示为0,而不是应该的INT_MAX/2

 

----------------------------------------------------------------------------------------------------------------------------------------------------

2.24

花了整整1个小时零40分钟调试,终于正确计算出目标矩阵和前驱矩阵。

其中前驱矩阵的错误调试花了大量的时间: 主要由于不能抽象考虑出错误点,只能通过单步调试发现错误,而错误又在比较后的循环中。

错误是:

if (target[i][j] > result.m_pDis[i][k] + graph.m_pMatrix[k][j])
                    {
                        target[i][j] = result.m_pDis[i][k] + graph.m_pMatrix[k][j];
                       
                        if (i!=j)
                            if (target[i][j]<result.m_pDis[i][j]) result.m_pPreVex[i][j] = k;
                    }

对于此处需要记录前驱结点的代码(原以为这么简单的代码,都花了大量的时间去调试。。。。。。。。)

原本只需要写: result.m_pPreVex[i][j] = k; 即可。

但是需要考虑的情况有:

(1)当i=j时,其前驱永远为-1

(2)对于m+1阶矩阵target的元素值与前一阶矩阵result.m_pPreVex相比没有变化时,不需要修改其前驱矩阵的元素。但是需要注意到是这样做的前提是初始化前驱矩阵时,不是全部初始化为-1,而是对直接连通的两点的前驱先设置好,如dis[i][j] = 8, 那么m_pPreVex[i][j] =i。因此,在初始化时,需要添加这样的语句:

 

if (i!=j && graph.m_pMatrix[i][j]<INT_MAX/2) result.m_pPreVex[i][j] = i;

不然的话,

================================

这个程序注意有一个重要的问题是对无穷大是使用INT_MAX/2,但是对于一些浮点类型的输入,就可能有问题了。

 

实验印证了我昨天的想法是正确的,那就是直接在原来的矩阵上运算,而不是用target矩阵来缓存下一阶的矩阵,是可以计算出正确的结果,连前驱矩阵也是可以计算出来的。我昨天花了一个小时的思考是没有浪费的,这可以说是一个创新点了,如果没有人发现的话。因为最短路径的渐进性质,使得最终的结果趋近于正确解。 而不需要额外的空间。

 

=========================================

利用最后一节时间写完了Floyd-Warshall,待调试。。。写完后要好好总结一下,这几个算法。

写一个算法不难,关键要理解这个算法。

错误在于k从0开始,而不是从1开始,因为k表示的是中间节点的序号,需要从第0个结点开始,逐个加入到图中。

 

----------------------------------------------------------------------------------------------------------------------------------------------------

2.25

事实上,在Floyd算法中应用昨晚得出的结论,即直接在原来的dis矩阵中做Update是可以得到最终的正确结果的,但是中间过程中的矩阵将会更快收敛到最短路径上。

而使用这个方法时,前驱矩阵也能得出最终结果,但是中间过程中的矩阵会出现与正常过程有差异的变动。

 

今晚从第二节起开始复习Johnson和写。第三节开始写 主要将图的结点列表改为动态表容器,居然才刚刚弄完。。。。慢。。。

 

----------------------------------------------------------------------------------------------------------------------------------------------------------

2.26

第一节时间写了图的增删边点并调试。

对结构体,类对象等的使用一定要非常注意,多用指针,少用变量,今天就有一个错误是因为不用指针导致的。

    void AddEdge(int fromVex, int toVex, E weight)
    {
        AdjVertexNode  vex = (*m_pVex)[fromVex];
        AdjEdgeNode* newEdge = new AdjEdgeNode;
        newEdge->toVex = toVex;
        newEdge->weight = weight;
        newEdge->next = vex.first;
       
        vex.first = newEdge;
    }

 

这段代码是不会得到正确的结果的,因为代码中vex是一个结构体对象,这个对象由 (*m_pVex)[fromVex]复制得到,而并非原来的图对象中的点对象,于是插入的边并不会出现在图中。使用指针才能得到正确的结果。

所以说C/C++中有指针存在的确非常方便,但是稍不注意便将导致错误发生。。。

 

第二节完成了Johnson算法,应该没有出错。但是突然发现复制构造函数有问题。

写好了构造函数 也写了转换函数。还有问题 明天待查

 

----------------------------------------------------------------------------------------------------------------------------------------

2.27

找到了昨晚的错误,原来是函数名不匹配,其实目前写代码还是很马虎 比较粗心。

 

 

*记录一个问题,应该之前也记录过,但是看来印象还是不深刻:

    virtual bool IsExistEdge(int fromVex, int toVex) = 0;
    virtual int AddVex() = 0;
    virtual void AddEdge(int toVex, int fromVex, E weight) = 0;
    virtual void DeleteEdge(int toVex, int fromVex) = 0;
    virtual void DeleteVex(int delVex) = 0;

当=0 不写的时候,会出如下链接错误。

error LNK2001: unresolved external symbol "public: virtual bool __thiscall Graph<int>::IsExistEdge(int,int)" (?IsExistEdge@?$Graph@H@@UAE_NHH@Z)

 

===============================================

Johnson算法有问题!!!花了半个小时做测试,找出了一个Bellman-Ford算法的Bug!!!

 

一个经验是,完成这个多组件协作的程序,真的是牵一发而动全身,必须进行严格的单元测试,并且对接口要进行完全的精准的定义,不可随便更改,否则肯定会导致非常多的BUg!!!

 

=============================================================

继续查找Johnson中算法的问题。

其实很明显Johnson本身是不会有问题的,唯一的可能是其他的组件有问题。

查到是Dijkstra有问题。

而问题出在优先队列Dequeue中,并没有获得正确的最小项。明日再解~~~~

 

-------------------------------------------------------------------------------------------------------------------------------------------------

2.28

 

20:10--20:23 调试出了昨晚优先队列的错误,错误发生在优先队列的Insert方法中。

由于前期思路不明确,导致Insert方法是:

(1)在堆尾插入一个0

(2)调用Modify将其修改成要改的key值。

 

但是由于Modify方法中,修改位置不确定,因此将根据key与原来key的大小相比较,来确定是向上维护还是向下维护(堆的性质)

而明显插入方法是需要向上维护。而根据错误的方法,将key与0比较,由于key比原来的key(0)大,因为需要向下维护,这就导致了错误的发生。

 

===================================================================================

 

至此,图算法一节已经全部完成。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值