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)大,因为需要向下维护,这就导致了错误的发生。
===================================================================================
至此,图算法一节已经全部完成。