• C++ 算法


    C++ 算法

    算法概念

    算法是特定问题求解步骤的描述

    在计算机中表现为指令的有限序列

    算法是独立存在的一种解决问题的方法和思想。

    对于算法而言,语言并不重要,重要的是思想。

    算法和数据结构区别

    数据结构只是静态的描述了数据元素之间的关系

    高效的程序需要在数据结构的基础上设计和选择算法

    程序=数据结构+算法 

    总结:

          算法是为了解决实际问题而设计的

          数据结构是算法需要处理的问题载体

          数据结构与算法相辅相成

    算法特性

    输入

                算法具有0个或多个输入

    输出

                算法至少有1个或多个输出

    有穷性

                算法在有限的步骤之后会自动结束而不会无限循环

    确定性

                算法中的每一步都有确定的含义,不会出现二义性

    可行性

                算法的每一步都是可行的

    算法效率的度量

    1、事后统计法

           比较不同算法对同一组输入数据的运行处理时间

    缺陷

           为了获得不同算法的运行时间必须编写相应程序

           运行时间严重依赖硬件以及运行时的环境因素

           算法的测试数据的选取相当困难

    事后统计法虽然直观,但是实施困难且缺陷多。

    2、事前分析估算

           依据统计的方法对算法效率进行估算

           影响算法效率的主要因素

           算法采用的策略和方法

                  问题的输入规模

                  编译器所产生的代码

                  计算机执行速度

    #define _CRT_SECURE_NO_WARNINGS
    
    #include <iostream>
    #include <string>
    
    //算法最终编译成具体的计算机指令
    //每一个指令,在具体的计算机上运行速度固定
    //通过具体的n的步骤,就可以推导出算法的复杂度
    
    long sum1(int n)
    {
        long ret = 0;                         
        int* array = (int*)malloc(n * sizeof(int)); 
        int i = 0;  
    
        for(i=0; i<n; i++)   
        {
            array[i] = i + 1;
        }
    
        for(i=0; i<n; i++) 
        {
            ret += array[i];
        }
    
        free(array); 
    
        return ret; 
    }
    
    long sum2(int n)
    {
        long ret = 0;
        int i = 0;
    
        for(i=1; i<=n; i++)
        {
            ret += i;
        }
    
        return ret;
    }
    
    long sum3(int n)
    {
        long ret = 0;
    
        if( n > 0 )
        {
            ret = (1 + n) * n / 2; 
        }
    
        return ret;
    }
    
    
    void mytest()
    {
        printf("%d
    ", sum1(100));
        printf("%d
    ", sum2(100));
        printf("%d
    ", sum3(100));
    
        return;
    }
    
    int main()
    {
        mytest();
    
        system("pause");
        return 0;
    }
    int func(int a[], int len)
    {
        int i = 0;
        int j = 0;
        int s = 0;
    
        for(i=0; i<len; i++) n
        {
            for(j=0; j<len; j++) n
            {
                s += i*j;  //n*n
            }
        }
        return s; 
    }
    //n*n

    注意1:判断一个算法的效率时,往往只需要关注操作数量的最高次项,其它次要项和常数项可以忽略。

    注意2:在没有特殊说明时,我们所分析的算法的时间复杂度都是指最坏时间复杂度。

    2、大O表示法

    算法效率严重依赖于操作(Operation)数量

    在判断时首先关注操作数量的最高次项

    操作数量的估算可以作为时间复杂度的估算

    O(5) = O(1)

    O(2n + 1) = O(2n) = O(n) 

    O(n2+ n + 1) = O(n2)

    O(3n3+1) = O(3n3) = O(n3)

    常见时间复杂度

    关系

    3、算法的空间复杂度

    算法的空间复杂度通过计算算法的存储空间实现

    S(n) = O(f(n))

    其中,n为问题规模,f(n))为在问题规模为n时所占用存储空间的函数

    大O表示法同样适用于算法的空间复杂度

    当算法执行时所需要的空间是常数时,空间复杂度为O(1)

    空间与时间的策略

               多数情况下,算法执行时所用的时间更令人关注

               如果有必要,可以通过增加空间复杂度来降低时间复杂度

               同理,也可以通过增加时间复杂度来降低空间复杂度

    #define _CRT_SECURE_NO_WARNINGS
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    /*
        问题: 
        在一个由自然数1-1000中某些数字所组成的数组中,每个数字可能出现零次或者多次。
        设计一个算法,找出出现次数最多的数字。
    */
    
    // 方法1: 排序,然后找出出现次数最多的数字
    // 排序,然后找出出现次数最多的数字
    
    // 方法2: 把每个数字出现的次数的中间结果,缓存下来;在缓存的结果中求最大值
    void search(int a[], int len)
    {
        int sp[1000] = {0};
        int i = 0;
        int max = 0;
    
        for (i = 0; i < len; i++)
        {
            int index = a[i] - 1;
            sp[index]++;
        }
        for (i = 0; i < 1000; i++)
        {
            if (max < sp[i])
            {
                max = sp[i];
            }
        }
        for (i = 0; i < 1000; i++)
        {
            if (max == sp[i])
            {
                printf("%d
    ", i + 1);
            }
        }
    }
    
    
    void mytest()
    {
        int array[] = {1, 1, 3, 4, 5, 6, 6, 6, 2, 3};
        search(array, sizeof(array)/sizeof(array[0]));
    
        return;
    }
    
    int main()
    {
        mytest();
    
        system("pause");
        return 0;
    }

    1  大部分程序的大部分指令之执行一次,或者最多几次。如果一个程序的所有指令都具有这样的性质,我们说这个程序的执行时间是常数。
     logN   如果一个程序的运行时间是对数级的,则随着N的增大程序会渐渐慢下来,如果一个程序将一个大的问题分解成一系列更小的问题,每一步都将问题的规 模缩减成几分之一 ,一般就会出现这样的运行时间函数。在我们所关心的范围内,可以认为运行时间小于一个大的常数。对数的基数会影响这个常数,但改变不会太 大:当N=1000时,如果基数是10,logN等于3;如果基数是2,logN约等于10.当N=1 00 000,logN只是前值的两倍。当N时原来的两倍,logN只增长了一个常数因子:仅当从N增长到N平方时,logN才会增长到原来的两倍。
     N  如果程序的运行时间的线性的,很可能是这样的情况:对每个输入的元素都做了少量的处理。当N=1 000 000时,运行时间大概也就是这个数值;当N增长到原来的两倍时,运行时间大概也增长到原来的两倍。如果一个算法必须处理N个输入(或者产生N个输出), 那么这种情况是最优的。
     NlogN  如果某个算法将问题分解成更小的子问题,独立地解决各个子问题,最后将结果综合起来 ,运行时间一般就是NlogN。我们找不到一个更好的形容, 就暂且将这样的算法运行时间叫做NlogN。当N=1 000 000时,NlogN大约是20 000 000。当N增长到原来的两倍,运行时间超过原来的两倍,但超过不是太多。
     
    N平方
     如果一个算法的运行时间是二次的(quadratic),那么它一般只能用于一些规模较小的问题。这样的运行时间通常存在于需要处理每一对输入 数据项的算法(在程序中很可能表现为一个嵌套循环)中,当N=1000时,运行时间是1 000 000;如果N增长到原来的两倍,则运行时间将增长到原来的四倍。
     N三次方  类似的,如果一个算法需要处理输入数据想的三元组(很可能表现为三重嵌套循环),其运行时间一般就是三次的,只能用于一些规模较小的问题。当N=100时,运行时间就是1 000 000;如果N增长到原来的两倍,运行时间将会增长到原来的八倍。
     2的N次方  如果一个算法的运行时间是指数级的(exponential),一般它很难在实践中使用,即使这样的算法通常是对问题的直接求解。当N=20时,运行时间是1 000 000;如果增长到原来的两倍时,运行时间将是原时间的平方!

    log log N 可以看作是一个常数:即使N很多,两次去对数之后也会变得很小

    以前光看了n多排序算法,知道仅通过比较的排序算法一共两种复杂度

    O(N2)或O(N*lgN),

    由于高数学的不好,之前一看到后者就放弃了思考,没有真正研究为什么会有个lgN,

    这两天工作不是很忙,看了一下基础知识,有了一定的认识,算是初步搞清楚了原因.

    写在这里算是一个记录,如果有问题也请大家指正.

    说到N*lgN的算法大致上有几种:堆排序,归并排序,快速排序.

    由于学习数据结构的时候老师讲过快速排序,(其实各种都讲过,我只记住了这种)我现在还是非常有印象的,

    这几种排序实际都有一个共同点,这个共同点让他们有了lgN的特性.

    都是使用了分治算法,把大集合通过分治,形成小集合,小集合的排序再次递归更小集合,直到1个(还可能是2个或3个)元素为止.

    这样对于整个集合来讲,每次递归都是处理2个元素数量是1/2当前元素数量的新集合,

    f(x) = f(x/2) + a (有限极小数)

    这个特点在堆排序和归并排序中尤为突出,他们是绝对的遵循2分法的.而快速排序结果是随机的,如果点背,可能出现O(N*N)的可能性

    下面用堆排序为例说明一下NlgN:

    为了让更多人明白,我简单把堆排序说一下:

    堆排序就是把原来输入的值串,当成一棵完全2叉树,每次找最大值的时候,都是把数的左右子节点比较,把大于自己的最大的一个跟自己互换,找到一个后,重新找剩下的n-1个,一直到最终找完所有节点.

    由于递归使用的是深度优先,每次都会从最底层往上找起,每次找的次数假设是F(x),则其需要找两个子树F(x/2)并且等两个子树处理完后,比较2次(子树的根比较一次,跟自己比较一次)如果连移动都算上,是3次操作

    值的注意的是,由于最初排列过了以后,找子树的时候只要找那颗被破坏了的子树即可,

    另一颗排过序了的不需要再找了. (这个我自己都感觉说的不明白,如果实在不行,大家再去看看相关资料吧)

    这样分析下来,找第x个节点的操作次数为: f(x) <= f((x-1)/2) +3 (当然,我理解算成 + 2也行,x-1是把根去掉)

    由于当x为2的整次方倍 + 1 的时候,正好是这个数值,当其他的情况 也不大于这个值

    所以我们可以就使用最大值的情况 f(x) = f((x-1)/2) + 3; 为了计算更容易

    直接 f(x) = f(x/2) + 3

     f(x/2) = f(x/4) + 3

    ......

     f(x/2m-1) = f(x/2m) + 3   (2m>=x)  <= f(1)+3 < 3;

    由于一共m个算式 加起来是 f(x) = 3m 而 m = log(2)(x)

    f(x)=3log(2)(x);

    而计算f(1)+f(2) + ... + f(n)的时候,我们把他分城 m段(m= log(2)(n)) (分别为1,2,4,8,...2m-1)个元素(当然最后一端可能没有那么多)

    求他们的和的话就是

    2m-1(m-1) + 2m-2(m-2) + ....<2m-1m + 2m-2m + ... < 2mm 而m = log(2)(N)

     2mm =  2log(2)(n)*log(2)(n) = N * log(2)(N)

    得证 哈哈

  • 相关阅读:
    【译】用 Rust 实现 csv 解析-part3
    【译】用 Rust 实现 csv 解析-part2
    【译】用 Rust 实现 csv 解析-part1
    【译】Rust 中的 char 和 Go 中的 rune 有什么区别?
    【译】TCP/IP 网络编程基础系列-数值编码
    【译】我最喜欢的 Rust 函数签名
    Rust 学习之运算符重载
    java.util.ConcurrentModificationException: null 问题分析解决
    2020年
    科目三夜间灯光模拟
  • 原文地址:https://www.cnblogs.com/lsgxeva/p/7794280.html
Copyright © 2020-2023  润新知