• 数据结构前言


    1. 什么是数据结构


    数据结构(Data Structure)是计算机存储、组织数据的方式,指相互之间存在一种或多种特定关系的 数据元素的集合。(该如何最佳的方式存储对应的数据,在内存当中对数据进行增删查改)

    2.什么是算法?


    算法(Algorithm):就是定义良好的计算过程,他取一个或一组的值为输入,并产生出一个或一组值作为 输出。简单来说算法就是一系列的计算步骤,用来将输入数据转化成输出结果。(对数据进行处理运算,达到自己想要的结果)

    3.算法的时间复杂度和空间复杂度


    3.1 算法效率

    3.1.1 如何衡量一个算法的好坏

    如何衡量一个算法的好坏呢?比如对于以下斐波那契数列:

    如何衡量一个算法的好坏呢?比如对于以下斐波那契数列:

    1. long long Fib(int N)
    2. {
    3. if(N < 3)
    4. return 1;
    5. return Fib(N-1) + Fib(N-2);
    6. }

    斐波那契数列的递归实现方式非常简洁,但简洁一定好吗?那该如何衡量其好与坏呢?

    3.1.2 算法的复杂度

    算法在编写成可执行程序后,运行时需要耗费时间资源和空间(内存)资源 。因此衡量一个算法的好坏,一般 是从时间和空间两个维度来衡量的,即时间复杂度和空间复杂度。 时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间。在计算 机发展的早期,计算机的存储容量很小。所以对空间复杂度很是在乎。但是经过计算机行业的迅速发展,计 算机的存储容量已经达到了很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复杂度。

    4.时间复杂度


    4.1 时间复杂度的概念

    时间复杂度的定义:在计算机科学中,算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。一 个算法执行所耗费的时间,从理论上说,是不能算出来的,只有你把你的程序放在机器上跑起来,才能知 道。但是我们需要每个算法都上机测试吗?是可以都上机测试,但是这很麻烦,所以才有了时间复杂度这个 分析方式。一个算法所花费的时间与其中语句的执行次数成正比例,算法中的基本操作的执行次数,为算法 的时间复杂度。

    即:找到某条基本语句与问题规模N之间的数学表达式,就是算出了该算法的时间复杂度。

    1. // 请计算一下Func1中++count语句总共执行了多少次?
    2. void Func1(int N)
    3. {
    4. int count = 0;
    5. for (int i = 0; i < N ; ++ i)
    6. {
    7. for (int j = 0; j < N ; ++ j)
    8. {
    9. ++count;
    10. }
    11. }
    12. for (int k = 0; k < 2 * N ; ++ k)
    13. {
    14. ++count;
    15. }
    16. int M = 10;
    17. while (M--)
    18. {
    19. ++count;
    20. }
    21. printf("%d\n", count);
    22. }

    如果直接来算,Func1 执行的基本操作次数 :

    F(N)= N ^ 2 + 2*N +10

    当式子很长并且N很大的时候,F(N)就难以算出来,因此:

    我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要大概执行次数,那么这里我们使用大O的渐进表示法。(抓大头取最高阶数的项,取具有决定性结果的那一项)

    所以此题的时间复杂度为:

    O(N^2)

    4.2常见时间复杂度计算举例

    1. // 计算Func2的时间复杂度?
    2. void Func2(int N)
    3. {
    4. int count = 0;
    5. for (int k = 0; k < 2 * N ; ++ k)
    6. {
    7. ++count;
    8. }
    9. int M = 10;
    10. while (M--)
    11. {
    12. ++count;
    13. }
    14. printf("%d\n", count);
    15. }

    O( N )

    1. // 计算Func3的时间复杂度?
    2. void Func3(int N, int M)
    3. {
    4. int count = 0;
    5. for (int k = 0; k < M; ++ k)
    6. {
    7. ++count;
    8. }
    9. for (int k = 0; k < N ; ++ k)
    10. {
    11. ++count;
    12. }
    13. printf("%d\n", count);
    14. }

    无法区分M和N的大小关系,所以都保留

    O( M+N )

    1. // 计算Func4的时间复杂度?
    2. void Func4(int N)
    3. {
    4. int count = 0;
    5. for (int k = 0; k < 100; ++ k)
    6. {
    7. ++count;
    8. }
    9. printf("%d\n", count);
    10. }

    最大项为常数,就为O(1),1的意思为常数

    O(1)

    1. // 计算strchr的时间复杂度?
    2. const char * strchr ( const char * str, int character );

    strchr意思是在一个字符串中找出一个字符,有点类似于strstr(字符串中查找字符串)

    通过上面我们会发现大O的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数。 另外有些算法的时间复杂度存在最好、平均和最坏情况:

    例如:

    在一个长度为N数组中搜索一个数据x

    最好情况:1次找到

    最坏情况:N次找到

    平均情况:N/2次找到 

    实际中一般情况关注的是算法的最坏运行情况,所以数组中搜索数据时间复杂度为O(N)

    时间复杂度计算时,是一个稳健保守的预期

    O( N )

    1. // 计算BubbleSort的时间复杂度?
    2. void BubbleSort(int* a, int n)
    3. {
    4. assert(a);
    5. for (size_t end = n; end > 0; --end)
    6. {
    7. int exchange = 0;
    8. for (size_t i = 1; i < end; ++i)
    9. {
    10. if (a[i-1] > a[i])
    11. {
    12. Swap(&a[i-1], &a[i]);
    13. exchange = 1;
    14. }
    15. }
    16. if (exchange == 0)
    17. break;
    18. }
    19. }

    因为要进行两两比较以第一个数向后比较,比较的数字随着比较的进行会越来越少(n-3,n-2,n-1),所以是一个等差数列,而等差数列的公式就是(N-1)*N/2 所以化简来看就是:

    O(N^2)

    1. // 计算BinarySearch的时间复杂度?
    2. int BinarySearch(int* a, int n, int x)
    3. {
    4. assert(a);
    5. int begin = 0;
    6. int end = n-1;
    7. // [begin, end]:begin和end是左闭右闭区间,因此有=号
    8. while (begin <= end)
    9. {
    10. int mid = begin + ((end-begin)>>1);
    11. if (a[mid] < x)
    12. begin = mid+1;
    13. else if (a[mid] > x)
    14. end = mid-1;
    15. else
    16. return mid;
    17. }
    18. return -1;
    19. }

    因为时间复杂度是要计算最坏的情况,而二分查找2最坏的情况就是

    查找的区间数据的个数如下:

    N------------------第一次查找

    N/2---------------第二次查找

    N/2/2-------------第三次查找

    N/2/2/2-----------第四次查找 

    ....

    N/2/2...../2/2 = 1

    (每次查找减半,直到只剩一个数)


    最坏情况,查找区间缩放只剩一个值时,就是最坏

    最坏情况下查找多少次? 除了多少次2,就查找了多少次
    假设查找x次,2^x = N,x = log(2为底数)N

    (附加)二分查找的差距


    我们目前看来可能二分查找不就是对半分嘛,也不会很精妙啊。下面我们可以看一下差距

    假设我们写了两个程序,一个是暴力查找搜索数,一个是二分查找搜索数:

            方法                                搜索基数(总数)                             最差情况搜索次数                                    

    暴力搜索:O(N)                    1000/100W                                          1000/2000

    二分查找:O(log(2)N)                1000/100W                                             10/20                   

    我们可以看出我们如果用暴力搜索一个数的时候最次的情况就需要1000次才能找到,而100W次的时候那就需要100W次的搜索,那将是巨大的开销。

    而我们用了二分查找,1000次就需要搜索10次:2^10=1000

    而100W次呢?只需要2^20-->1024*1024=100W

    1. // 计算阶乘递归Fac的时间复杂度?
    2. long long Fac(size_t N)
    3. {
    4. if(0 == N)
    5. return 1;
    6. return Fac(N-1)*N;
    7. }

    总共会调用N+1次,递归算法是多次调用的累加

    所以答案为:O(N)

    1. // 计算斐波那契递归Fib的时间复杂度?
    2. long long Fib(size_t N)
    3. {
    4. if(N < 3)
    5. return 1;
    6. return Fib(N-1) + Fib(N-2);
    7. }

    我们把具体fib的函数给展开

    可以得到是以2的次方累加直到(N-1)

    所以是O(2^N)

    我们来做一道力扣题

    面试题 17.04. 消失的数字 - 力扣(LeetCode)

    因为是顺序的,所以我们可以想到类似单身狗的问题,如果想知道单身狗怎么求解可以查看我上条博客如何用C语言找到单身狗-CSDN博客

    看完这道题后就会有思路啦:因为0^任何数都为任何事,而相同的数异或为0。

    1. //原始人办法
    2. // int missingNumber(int* nums, int numsSize) {
    3. // int N = numsSize;
    4. // int ret = N * (N + 1) / 2;
    5. // for (int i = 0; i < N; i++)
    6. // {
    7. // ret -= nums[i];
    8. // }
    9. // return ret;
    10. // }
    11. //异或办法
    12. int missingNumber(int* nums, int numsSize)
    13. {
    14. int n = numsSize;
    15. int x = 0;
    16. for (int i = 0; i <= n; i++)
    17. {
    18. x^= i; //0异或了n以内的数再异或一次缺失的数,得到的就是缺失的数
    19. }
    20. for (int i = 0; i < n; i++)
    21. {
    22. x ^= nums[i];
    23. }
    24. return x;
    25. }

  • 相关阅读:
    C++的类和new和delete和菱形继承机制
    【Error】mysql报错:[Err] 1273 - Unknown collation: ‘utf8mb4_0900_ai_ci‘
    【Redis场景5】集群秒杀优化-分布式锁
    《乔布斯传》英文原著重点词汇笔记(三)【 chapter one】
    (附源码)小程序springboot心理治愈 毕业设计 041506
    AlGaN/GaN HFET 五参数模型
    如何实现Debian工控电脑USB接口安全管控
    vue小技能:使用渲染函数编写组件
    小说接龙小程序
    vwware docker安装seata
  • 原文地址:https://blog.csdn.net/CatShitK/article/details/134063784