减治法(一)
这篇文章将讨论:
1) 减治法的思想和策略
2) 几个数据结构里面经典的使用减治策略的算法:插入排序,深度和广度优先查找,拓扑排序(都是减一治的)
通过 1) 2)明白减治策略的基本思想和方法,也对经典数据结构做一番新的审视,从减治策略的角度来重新看待这些算法。
而在后面,将继续花几篇文章讨论减治策略的其他问题:排列问题,子集问题,减常因子算法,减可变规模算法。
----------------------------------------------------------------------------------------------------------------------------------------------------
减治技术利用了一个问题给定实例的解和同样问题较小实例的解之间的某种关系。一旦建立了这种关系,就可以从顶至下递归的来用该关系,也可以从底至上非递归的来运用该关系:
1)减去一个常量
2)减去一个常量因子
3)减去的规模是可变的
1) 一般来说减去的一个常量是1,即如果不断地解决n-1规模的问题就能解决n规模的问题,(偶而也有减2的,比较少)
比如求a^n的值,
既可以递归的从上到下求解,也可以非递归的从下往上构造(连续乘法,注意方法和蛮力一样,但思考问题的角度不一样)
2) 一般来说减去的一个常数因子是2(即将原问题规模分为2),其实减常因子的减治法可以看做是分治的变种,只不过它只对划分子规模后的一个部分求解。
例如仍然是求a^n,我们可以这样来思考:
3)对于减可变规模的例子,那就更少了,因为效率越高的算法显然越难找到。
一个例子是欧几里得算法,前面也写过了:
总之,减治的3种方法,以及一个简单的例子就像上面所述。
-------------------------------------------------------------------------------------------------------------------------------------------------
1,插入排序
1) 最简单的排序方法,写过,也很简单。说下怎么从减一治的策略来思考:
对A[0...n-1]排序,如果你已经对0到n-2排好了序,那么将n-1号插入到合适的位置即可(注意后面的要移动)
这就递归的定义了插入排序的过程,当然,一般从底向上非递归的构造效率更高。递归和非递归的版本都可以写,略过。
2)简单的扩展下:折半插入排序,在一个元素要求插入时,在寻找它的插入位置时,用折半查找来寻找,复杂度是nlogn
3)shell排序:插入排序的改进,递减插入步长,比如分别以13,4,1为步长来插入排序。
有机会写一下。由于它的步长原因,可以看出shell排序是不稳定的。(插入排序是稳定的,它的步长是1)
-------------------------------------------------------------------------------------------------------------------------------------------------
2,深度优先查找和广度优先查找
1)深度优先查找
更正:前面写的深度优先查找是错误的,那本书上讲的也是错误的。见前面的文章,我已经确认过了。
那本书上是这样写的:
初始化:第一个顶点入栈while(栈不空)
{
将当前栈顶元素出栈
将出栈的这个顶点的所有未被访问的邻接点入栈
}
其结果是把出栈的顺序作为深度优先遍历的结果,错错错!!!!!!!!!!!!!!!!!!!这是形式上完全照抄广度优先遍历的写法,广度优先遍历这样是对的(把栈改成队列,形式完全一样)。它这样做的核心思想似乎是每个顶点出栈时要把它未被访问的邻接点入栈,似乎这样就能导致一个顶点扩展出去的顶点全部访问完才能回到跟它同一批入栈的下一个顶点,事实上,大错特错!!!!!
错误:
a 不要把出栈顺序当做访问顺序,而应该把进栈顺序当做访问顺序,重写一个程序(见下面)
b 不要冒然出栈,只有当一个顶点是死端(它没有邻接点或邻接点已经都被访问过了)了,才将它出栈。
c 出栈时不是将它所有的顶点都入栈,而是仅仅将它的一个未被访问的顶点入栈。
其正确的算法是这样的:(用栈来实现)
初始化:第一个顶点入栈
while(栈不空)
{
检查当前栈顶元素是否为死端;
if(栈顶元素是死端)
出栈;
else(不是死端)
把它的一个未被访问的邻接点入栈;
}
妈的,害我错误了这么久。
当然,递归的来定义会简单很多,不管什么死端不死端,直接递归的定义深度优先搜索,略。
另外一点就是这里又扩展了一点,树向边和回边:
进栈顺序(第一个下标): a c d f b e | g h i j (深度遍历被访问顺序)
出栈顺序(第二个下标): d e b f c a | j i h g
树向边:在检查栈顶A时,若A不是死端,那么将A的一个未被访问的邻接点B入栈,这就有了树向边A-B
回边:若检查栈顶A时,A是死端,则A到它的非父节点的邻接点之间就存在一个回边。
见上图。
不含回边----无回路
2)广度优先查找
那本书上是对的,跟上述深度的错误代码一样,只是将栈改成队列。
也有树向边和回边。
不再详述。
BFS可以用来求2个顶点之间的最短距离,从其中一个开始遍历,当到达另一个时就停止,此时BFS树的根到第二个顶点之间的路径具有最小边数:
最后,一个总结:
-------------------------------------------------------------------------------------------------------------------------------------------------
3,拓扑排序
1) 从减一治的角度,其实就是传统的拓扑算法,就是每次从图中删除一个入度为0的顶点,见前面的一篇文章,很easy。
2) 来说一点有意思的,深度优先遍历中出栈的顺序反过来就是拓扑序列。请思考一下。
------------------------------------------------------------------------------------------------------------------------------------------------
总结:
减一治的思想和3种策略
3种经典的数据结构算法,从减一治的角度思考。
特别是深度优先的更正!!!!!