zoukankan      html  css  js  c++  java
  • 数据结构与算法分析(一)

    意识到数据结构和算法很重要,那还是源于去年保研期间的一些经历。也一直想着抽出一段时间来学习,无奈人忙事杂,先是练车学驾照耗费了一些时日,前段时间出了趟国,去东南亚几个国家走了一遭,玩了一个月,自然相当开心,这事以后也还会讲,再加上一直陆续做自己的个人网站,也一直没闲着。这不,自己的个人网站V1.0前天上线,最近也没有太多事情,就拿起书,正式的学。

    书用的是Mark Allen Weiss写的《数据结构与算法分析——C语言描述》,现在看了前两章,看得出语言相当精简,例子也都非常适宜,很详实,书本身也不厚,讲这么多的东西,理解起来自然也就有些难度。说回来,C语言还是大一的时候学的,当时也是第一次接触编程语言,学的多烂自己也清楚,这几年接触的语言,像Python和JavaScript等,抽象性都非常高,突然转到C,还有些不适应,因此在看这本书的时候,反而有很长的时间再温习C语言的书。

    废话少说,直接步入正题!

    具体的代码,除非自己觉得相当好的,否则就不贴了。

    第一章讲了一些基本的概念,其实是灌输一些思想:解决问题的方法也许有很多,在面临大量输入的情况下,不同算法的性能下降的厉害,因此仅仅写出一个可以工作的程序是远远不够的。话说的很犀利,这几乎就是我以前的观念,我想想了想,有这么几个原因:第一个就是自己接触到的问题输入量还不够大,因此算法的效率就不是那么明显,二是现在的计算机性能都非常的好,速度快到你还没意识到算法之间时间运行上的差异,它就结束了。

    所以,现实给我们提出的挑战就是:寻找一个性能优异,而且相当稳定,面临大量输入,依旧可以从容不迫地输出,而不至于崩溃的算法。

    书的第二章举了几个例子,怎么说呢,不寒而栗吧,待会会说到。

    还讲了一些数学的概念,都是级数相关的,个人认为比较重要的一个方法就是:数学归纳法。一个结论也许看起来很自然,但是证明起来难度却很大,数学归纳法包含了“递归”的思想,充分利用已有的信息,逐步的接近一个结论,用起来往往不是很难,毕竟从k到k+1之间的规律还是很好找的。有时有些问题从正面很难突破,借用数学归纳法,借力打力,得出结论。

    下面说递归。一个函数,用自己来定义自己,就是递归。递归是一种非常直白的思路,很符合人类思维的模式,用来解决一些看起来很复杂的问题有奇效,递归并不是循环,尽管它们看起来相似,如果往我来形容它们之间的差距,我会说:循环里的计算是等阶的,而递归是降阶的。递归就是把一个复杂的问题分解,一步步分解到一个简单的单元上,然后往回推,一步步就把原始的问题解决了。

    书里总结了几个关于递归的规律:

    1、基准情形,无需递归就能接触的,也就是最小的那个单元

    2、不断推进,每一次递归调用都必须使求解状况朝接近基准情形的方向推进,也就是降阶

    3、设计法则,假设所有的递归调用都能运行,递归如果中断,不仅无法分解到基准,而且无法由基准情形回推

    4、合成效益法则,也就是避免重复性的工作,这点很重要,这提示我们,递归好用,但并不适合所有的情况

    书的第二章开始着手具体的算法分析。算法是求解一个问题需要遵循的、被清楚地指定的简单指令的集合。计算机的思维模式和人不一样,我感觉算法就是是计算机的伟大之处,要知道,CPU上的那一个个的小开关,它们只会开和关,但一群开关提供的计算量却是惊人的。你只要给它一个清晰地、每一步都很简单的步骤,它就能给出答案。伟大!

    这一章的前半段讲的非常晦涩,说到底,还是大O分析,就是评估算法运行时间的最坏情况。为什么考虑最坏的情况?仔细想想,这种考虑不仅非常的巧妙,而且很实用。一来呢,平均情况不好把握,影响因素比较多,所以呢,干脆把变量的影响降到最低,直接假设为最坏状况吧,算出来的时间应该是算法运行所需要的最长时间,有了这个数,我们心里就有底了。有的时候我么需要这种极限的思维,因为极限的背后,往往有着比较简单的结论,而这些结论通常都是本质性的。

    常见的算法时间复杂度由小到大依次为:Ο(1)Ο(log2n)Ο(n)Ο(nlog2n)Ο(n2)Ο(n3)Ο(2n)Ο(n!)

    这个时候书里举出了一个反例,是用递归算法计算斐波那契数列。如下:

    int Fib(int N){

        if(N<=1){

            return 1;

        }

        else{

            return Fib(n-1)+Fib(n-2);

        }

    }

    起初我看到这里觉得这个算法还相当合理,逻辑也很清楚。但作者给出了它的大O分析,时间复杂度是指数级的,这样的算法是我们需要极力避免的。问题出在哪里?大量的重复计算。有个格言说得好“计算任何事情不要超过一次”。斐波那契有个非常明显的规律,就是前两位数字相加等于第三位数,依次递推,每一个位置的数字其实都已经包括了前面数字的信息,但是上述的递归算法却将每一位数都分解为最原始的最小单元,无疑浪费了很多的信息,也没用充分利用斐波那契的规律。尽管代码很短,却并非我们需要的。

    书里又给出了一个非常经典的问题:最大子序列和的解。

    书里给出了四种解法,时间复杂度一次从Ο(n3)> Ο(n2)>O(nlog2n)>O(n)

    //第一种

    int MaxSubsequence1(const int A[], int N){

        int Thissum, Maxsum=0;

        int i, j, k;

        for(i=0; i<N; i++){

            for(j=i; j<N; j++){

                Thissum = 0;

                for(k=i; k<=j; k++){

                    Thissum += A[k];

                }

                if(Thissum>Maxsum){

                    Maxsum = Thissum;

                }

            }

        }

        return Maxsum;

    }

    //第二种

    int MaxSubsequence2(const int A[], int N){

        int Thissum, Maxsum=0;

        int i, j;

        for(i=0; i<N; i++){

            Thissum = A[i];

            for(j=i+1; j<N; j++){

                if(Thissum>Maxsum){

                    Maxsum = Thissum;

                }

                Thissum += A[j];

            }

        }

        return Maxsum;

    }

    //第三种

    int Max3(int a, int b, int c){

        int max;

        if(a>b){

            if(a>c){

                max=a;

            }

            else{

                max=c;

            }

        }

        else{

            if(b>c){

                max=b;

            }

            else{

                max=c;

            }

        }

        return max;

    }

    int MaxSubsum(const int A[], int left, int right){

        int MaxLeftSum=0, MaxRightSum=0;

        int center, i;

        if(left==right){

            if(A[left]<0){

                return 0;

            }

            else{

                return A[left];

            }

        }

        center = (right+left)/2;

        MaxLeftSum = MaxSubsum(A, left, center);

        MaxRightSum = MaxSubsum(A, center+1, right);

        int MaxLeftBorderSum=0, MaxRightBorderSum=0;

        int LeftBorderSum=0, RightBorderSum=0;

        for(i=center; i>=left; i--){

            LeftBorderSum += A[i];

            if(LeftBorderSum>MaxLeftBorderSum){

                MaxLeftBorderSum = LeftBorderSum;

            }

        }

        for(i=center+1; i<=right; i++){

            RightBorderSum += A[i];

            if(RightBorderSum>MaxRightBorderSum){

                MaxRightBorderSum = RightBorderSum;

            }

        }

        return Max3(MaxLeftSum, MaxRightSum, MaxLeftBorderSum+MaxRightBorderSum);

    }

    int MaxSubsequence3(const int A[], int N){

        return MaxSubsum(A, 0, N-1);

    }

    //第四种

    int MaxSubsequence4(const int A[], int N){

        int Thissum=0, Maxsum=0;

        int i;

        for(i=0; i<N; i++){

            Thissum += A[i];

            if(Thissum>Maxsum){

                Maxsum=Thissum;

            }

            else if(Thissum<0){

                Thissum = 0;

            }

        }

        return Maxsum;

    }

    第三种算法用到了“分治”的策略,递归的求解,将大问题分而化之,再通过一些少量的计算将二者结合,非常巧妙地思想,极大的降低了时间复杂度。

    而第四种无疑更为奇妙了,完全就是线性的复杂度,也称作“联机算法”。

    经典的算法:对分查找,用到了二分的思想,也是分而化之,比较常见,不多说。

    欧几里得算法求解两数的最大公约数,也是经典算法了。

    书的练习题里,有一个关于最大子序列乘积的问题,很有意思。网上查了,才知道叫“动态规划”。

    int MaxSeqMul(int A[]){

        int max, min, tempMax, tempMin, temp, i;

        tempMax = tempMin = temp = A[0];

        for(i=1; i<M; i++){

            max=Max(A[i], tempMin*A[i], tempMax*A[i]);

            min = Min(A[i], tempMin*A[i], tempMax*A[i]);

            tempMax = max;

            tempMin = min;

            temp = Max(temp, max, 0);

        }

        return temp;

    }

    也是线性的时间。

    总结:其实看了这么多例子,感受应该说很深刻了。以前没有这个意识,觉得算法能写出来就算不错了,何曾想过这之间的效率差异这么大,也深深的佩服想出这些算法的先驱前辈,何等的智慧。这本书写的非常详实,由浅入深,有些例子虽然看懂了,还要静下心来慢慢的消化,书看来还是应该多读,否则井底之蛙,以为世界只有自己的眼光这么大。

    却道,此心安处是吾乡
  • 相关阅读:
    Visual Studio 2008中 在工具栏上添加"在文件中查找"
    WPF 获得文件夹路径/浏览文件夹路径
    WPF ListBox
    如何:创建和使用 C# DLL(C# 编程指南)
    (收藏)《博客园精华集》分类索引
    WPF 文件级资源(类似与使用CSS文件,然后引用CSS文件)
    [WPF/Silverlight]让INotifyPropertyChanged的实现更优雅一些
    WPF 动画
    WPF 鼠标移动到图片变大,移开还原,单击触发事件效果
    Regsvr32.exe 用法
  • 原文地址:https://www.cnblogs.com/lucifer25/p/6501887.html
Copyright © 2011-2022 走看看