算法-“许三多”方法

算法-“许三多”方法_第1张图片
许三多

最近在看 《士兵突击》,所以用了许三多这个名字来说说算法。
许三多算法是什么呢?记得高连长对许三多的评价:

他每做一件小事的时候,他都像救命稻草一样抓着,有一天我一看,嚯!好家伙!他抱着的是已经让我仰望的参天大树了。

这就是许三多,积少成多,算法也是这样,有一种算法设计思路很简单,就是根据问题的描述表示出来问题的逆过程,下面我就根据算法设计与分析基础来说说许三多方法--算法设计与分析基础书中的蛮力法。

正如我在第一篇算法-绪论中列出的简单的算法学习思路,我们把算法按照设计思路来分类,说到蛮力法,我们就把算法的基本问题都拿出来,来看看如何把这个思路用下来。

1. 排序问题

1.1选择排序

选择排序应该算是我以为的最简单的算法了,它每次找出一个最小(大)值,放到它该放到的位置,一共n个数字,我就循环n次,这样所有的数字就都排好了顺序,来看一下C语言的简单实现:

void SelectionSort(int a[], int n){
    int i, j, min;
    for(i = 0; i < n-1; i++){
        min = i;
        for(j = i+1; j < n; j++)
            if(a[j] < a[min])
                min = j;
        swap(a[i], a[min]);
    }
}

按照我在算法-分析一文中的分析方法,找出这个算法的基本操作,很明显,最里层的语句是a[j] < a[min],所以

1.2冒泡排序

虽然选择排序是我认为最简单的排序,因为没有学算法的时候让我写一个排序问题,我想到的就是选择排序,而冒泡排序则是我被教的第一个排序算法。冒泡排序的名字来自于对水泡的观察,大泡往上,小泡被挤下来,所以最后就会排好序,不管你信不信,反正我信。下面看的还是C语言的算法实现:

void BubbleSort(int a[], int n){
    for(int i = 0; i < n; i++)
        for(int j = 0; j < n-1-i; j++)
            if(a[j+1] < a[j])
                swap(a[j], a[j+1]);
}

和上面一样,我们可以分析出来相同的时间复杂度,甚至是相同的基本操作。我们再多看一点关于冒泡排序的,因为我们学习算法真的不能只是把问题解决了,许三多最后不也学会了举一反三嘛!我们来看看这段冒泡排序里是不是多了点什么?如果是已经排好序的序列,我们会发现,基本操作还是在做,浪费了不少的精力呀!为了解决这个问题,我们可以立个flag,如果好了就直接跳出来就好了,不比较了!再有就是和选择排序的一点对比,我们发现在冒泡排序里,交换次数特别多,尤其是如果要把正序的数反过来变成逆序的,那就和基本操作进行的次数一样多,而选择排序则很好只进行n-1次的交换,这也是选择排序的一点优势。

排序问题就说这么多,为了表示我学习算法导论的决心和意志(我们学习算法也要学习证明,明白了算法的正确性我们用着放心),我就简单的说一说这两个算法的证明,不过蛮力法的证明实在是很纠结,因为太明显了,问题就是这么描述的,我们会想这有什么好证明的,很明显嘛!不过我觉得还是说一点比较好:对于这两种排序,最终都会生成这样一个序列:
a[1] >= min(a[1], a[2]) >= min(a[1], a[2], a[3]) >= ... >= min(a[1], a[2], ..., a[n-1], a[n])
稍作变化也就是:
a[1] >= a[2] >= a[3] >= ... >= a[n-1] >= a[n]
因为a[1] >= min(a[1], a[2])取走右式中的较小值就剩下了左式,以此类推,对所有的都会成立,最后就生成了这个有序的序列。

2. 查找问题

2.1顺序查找

说实话这个没什么好说的吧,看看代码妥了:

int SequentialSearch(int a[], int n, int k){
    while(n--)
        if(a[n] == k) return n;
    return -1;
}
2.2穷举查找

这个说的是那些复杂度是随着输入规模按照指数增长的一类问题,但也属于查找,所以我没有把它分出去,这一类问题通常都是组合问题。
旅行商问题这是个无处不在的计算机算法问题,简单的说就是找到可以连接一系列点的最短路径,采用用许三多修路的精神确实可以再来试一试这个...把所有的序列都找出来,然后再来看最短的是哪个。
哈密顿回路问题,这个问题我一看还真有点熟悉,但是真的不知道谁是哈密顿,我只知道欧拉的那个奇点(有奇数个分边的点)可以帮我们找到一笔画,就是从七桥问题开始的,这个哈密顿环恰恰是这些一笔画的最短的那个,当然,欧拉和许三多合体解决这个问题也还是有可能的吧!!!
这两个问题再加上我们熟知的0-1背包问题都是我们很熟悉的NP难问题,目前已知的解法的复杂度均不是多项式。

2.3深度优先搜索和广度优先搜索

说实话,想要编排这几个内容还真的是很难,因为这两个已经算是算法了,一点都不是非常的明显,这两个算法都是穷举查找的实现方式,基本的概念我就不说了,我想来说说对于初学者来说比较难以理解的。
深度优先搜索的接触应该是比广度优先搜索要早上一点的,因为我们在学习C语言的函数调用的时候会接触到一种数据结构-栈,而且如果明白了C语言的函数调用的话就会很明白,函数自己是可以调用自己的,这就是深度优先搜索的实现要领,每次调用一次自己就会复制出完全相同的一个自己,然后在另一个环境下开始下一步操作。这就是递归,深度优先搜索就是建立在递归之上的。深度优先搜索的算法要领就是,只要能离家远一点,我就坚决不回头,就像是一次次的十字路口,只要有路我就走,对比广度优先搜索就是我把每条路都先走一段试试,不行的话就继续走远。

这不是具体的问题,代码不是很好说哈!

3. 其他问题

对于蛮力法,有个优势就是,只要有算法可以解决的问题,它都可以尽自己的一份力,只要管饭有耐心基本都可以算出来,还有许多问题,比如说字符串匹配,撇去KMP算法不说,我们蛮力匹配:

int StringMatch(char a[], n, char b[], m){
    int i, j;
    for(i = 0; i < n-m+1; i++){
        j = 0;
        while(j < m && b[j] == a[i+j] && j++)
            if(j == m)    return i;
    return -1;
    }
}

也是可以解决问题的。
还有最近对问题,找出许多点中最近的两个点,思路很简单,所有的距离都找出来就完了呗:

int ClosestPoints(int x[], int y[], int n){
    int d2 = MAX_DISTANCE;
    for(int i = 0; i < n; i++)
        for(int j = i+1; j < n; j++)
            d2 = min(d2, (x[i] - x[j])*(x[i] - x[j]) + (y[i] - y[j])*(y[i] - y[j]));
    return d2;
}

所以我们看到了蛮力法的能力,只要能让计算机明白问题,几乎都可以算。

小结

好好编代码,不要给了算法还写不出来,这才是人生之大悲呀!基础很重要!!!蛮力法说是算法也是算法,但是并没有用到什么设计技巧,只是最直接的思维过程。

你可能感兴趣的:(算法-“许三多”方法)