zoukankan      html  css  js  c++  java
  • 数据结构与算法入门

    在17年的时候曾经专门对数据结构跟算法进行了学习,不过,没有系统学完就半途而废了,当时我记得是学到了图相关的东东,转眼到19年过去两年了,对于这块的东东决定还是重拾起来从0开始系统完整的把它学完,不管是为了跳槽还是说为了工作,不能给自己技术树上留下遗憾。

    数据结构与算法概念:

    对于这些概念其实在大学都已经学过了,不过为了完整性还是得要大概对它们进行一个复习。

    数据结构研究的内容:

    定义:
    数据结构是计算机存储、组织数据的方式。
    数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。
    通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。
    数据结构往往同高效的检索算法和索引技术有关。

    其中它有以下两个维度的结构:

    逻辑结构:

    • 集合结构
    • 线性结构
    • 树形结构
    • 图形结构

    存储结构:

    • 堆栈
    • 队列
    • 数组
    • 二叉树
    • 图 

    算法研究的内容:

    定义:

    算法(Algorithm)是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,
    算法代表着用系统的方法描述解决问题的策略机制。也就是说,能够对一定规范的输入,在有限时间内获得所要求的输出。
    如果一个算法有缺陷,或不适合于某个问题,执行这个算法将不会解决这个问题。
    不同的算法可能用不同的时间、空间或效率来完成同样的任务。
    一个算法的优劣可以用空间复杂度与时间复杂度来衡量。

    空间复杂度:

    主要是指所写的算法在内存中占用的大小,不过由于目前的机器的内存越来越大,所以这个指标相比时间复杂度不是那么重要,如今看算法的好坏可能都是看时间复杂度。

    时间复杂度:

    定义:

    算法的效率: 算法所处理的数据个数n 的函数,关键代码的执行次数。用大O表示法,如:

    下面来看这两段伪代码的时间复杂度:

    代码一:

    function1() {
       for(int i = 0; i < n; i++) {
            for(int j = 0; j < n; j ++) {
            // dosomething;//这时间复杂度是n^2
            }   
        }
    
        for(int i = 0; i < n; i++) {
        //dosomething   //这时间复杂度是n
        }
    
       // dosomething //这时间复杂度为1
    }

    该代码的O(n) = n^2 + n + 1。

    代码二:

    function2() {
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < n; j ++) {
              // dosomething; //这时间复杂度为n^2
            } 
    }

    FAQ:那以上两种算法,它们俩的时间复杂度哪个更好呢?

    很明显代码二要比代码一要好嘛,其实在时间复杂度上它们俩是一样的,因为在n为无穷大时它们的时间复杂度都是n^2。所以这里需要注意。

    如何把这两方面都控制到最合适?

    我们在大学学习时都会听到这样一句话:“数据结构+算法=程序”,而衡量一个程序的好坏除了时间复杂度和空间复杂度之外,其实还有一个很重要的因素,那就是应用场景。为啥这样说,下面以“变量交换”简单的算法来举例来说明这个“应用场景”的重要性,如下:

    第一种交换方法【最常用】:

    第二种方法:不用中间临时变量,而是采用相加法:

    也同样达到了两数的交换,节省了一个临时变量,但是有个弊端,如果是对象貌似就没法这样弄了。。

    第三种:上面第二种方法已经节省了空间,但时间上还有没有能进一步提升的呢?当然有,如下:

    这种写法貌似平常接触得比较少,先来说一下它的原理,这里^是异或符,表示如果位数不同则返回1,相同则返回0,所以先将a=5和b=6换成二进制为:

    a = 0101

    b = 0110

    而根据算法:

    此时

    a = 0011

    b = 0110

    再看第二句:

    此时

    a = 0011

    b = 0101

    再看第三句:

    此时

    a = 0110 = 6

    b = 0101 = 5

    所以通过位运算就将两数进行了替换了。

    好,那思考一下以上三种实现方式哪种方式更好呢?可能大部分人都说肯定是第三种嘛,时间空间都最优,性能也最好,这里要说的是!!要结合“应用场景”来说,假如我们是在app里面来使用,第一种要比第三种强,因为第一种的可读性比较好,而第三种可读性比较差。并且运行的性能也相差不了太多;但是!!如果此段代码是运行在像无人机、跑步机这样配置不太高的设备上,那么第三种要优于第一种,因为这种场景更加看重性能。

    线性表【数据结构的学习从它开始】:

    顺序存储结构:

    它长这样:

    而用顺序存储的结构用个专用词则称之为“顺序表【这样是所有程序员沟通起来的一个通用名称】”,而对于顺序表,里面的元素还有以下这种关系,如下:

    a1是a2的前驱,ai+1 是ai的后继,a1没有前驱,an没有后继
    n为线性表的长度 ,若n==0时,线性表为空表。

    知道了这些概念之后,那看下面这图是否是顺序表呢?

    其实它就是顺序表,因为它可以表示为这样的结构:

    class Students {
    Student[40];
    int size;
    }

    其实ArrayList就是典型的一个顺序表,它的源码的结构其实跟它类似,如下:

    优缺点:

    优点:
    尾插效率高,支持随机访问。
    缺点:
    中间插入或者删除效率低。

    可以用图来感受一下:

    下面用程序来模拟一下从数据中来查询数据,直接粗暴的轮循来查就成了:

    接着再来模拟一下数据插入:

    接下来则模拟一下删除操作:

     

    它的应用:
    数组
    ArrayList

    排序开端:

    像我们在玩斗牛时

    手中会发5张牌,然后对其进行排序, 可见每次排序的元素比较小,其实这种场景使用冒泡排序是最快的,下面先来定义一个牌的类:

    好,接下来比如我们收到的这几张牌,然后对其进行排序,超简单的:

    嗯,貌似非常之完美,采用系统的API对其进行排序不天经定义的嘛, 但是!!其实系统的这个sort()方法是非常“重”的一个实现,可以大致的瞅一下它的实现:

    也就是说采用系统的这个排序算法效率是比较低的,对于数据量这么小的排序来说性能不是太高,所以应该得用咱们自己的算法来实现,所以接下来就开始要接触线性表的排序算法了。

    蛮力法:

    啥叫蛮力法?先看一下它的定义:

    蛮力法(brute force method,也称为穷举法或枚举法)
    是一种简单直接地解决问题的方法,
    常常直接基于问题的描述,
    所以,蛮力法也是最容易应用的方法。
    但是,用蛮力法设计的算法时间特性往往也是最低的,【指的是n接近于无穷大时】
    典型的指数时间算法一般都是通过蛮力搜索而得到的 。(即输入资料的数量依线性成长,所花的时间将会以指数成长)

    貌似该算法应该是比较low的,指数级别的,但是!!!对于数据量比较小,像我们这个每次才五个数据的场景,用这个蛮力法的排序效率是最高的,下面来看一下这种算法长啥样?冒泡排序和选择排序都算,下面具体来看一下:

    冒泡排序:

    这里一点点来实现冒泡排序,先来说一下冒泡排序的第一步:

    比如要对“3,1,5,8,2,9,4,6,7”这个数列进行从小到大的冒泡排序,首先是两两数进行比较,第二个数如果比第一个数小则就交换位置,具体为:

    1、拿“3,1”进行比较,很显然3>1,所以此时对这俩数进行交换,就为:“1,3,5,8,2,9,4,6,7”;

    2、拿“3,5”进行比较,由于3<5,则不交换,就为:“1,3,5,8,2,9,4,6,7”;

    3、拿“5,8”进行比较,同样5<8,所以也不交换,就为:“1,3,5,8,2,9,4,6,7”;

    4、拿"8,2"进行比较,由于8>3,所以将两数进行交换,就为:“1,3,5,2,8,9,4,6,7”;

    5、拿"8,9"进行比较,由于8<9,所以不动,就为:“1,3,5,2,8,9,4,6,7”;

    6、拿"9,4"进行比较,由于9>4,所以交换,就为:“1,3,5,2,8,4,9,6,7”;

    7、拿"9,6"进行比较,由于9>6,所以交换,就为:“1,3,5,2,8,4,6,9,7”;

    8、拿"9,7"进行比较,由于9>7,所以交换,最终为:“1,3,5,2,8,4,6,7,9”;

    这样经过第一轮的俩俩比较就将最大的一个数浮到最后一位了,跟水冒泡一样从底部一点点往上浮,当然此时并未真正排好序,但是为了一步步实现,所以先不管这么多把这个步骤先实现了再之后进一步处理:

    好,接下来第二轮比较则应该将这个“9”排除再进行俩俩比较,其实也只要改这块的条件既可:

    所里这里再定义一个外层循环用它控制它,如下:

    而它的时间复杂度可以算一下:

    第一轮是要比较n-1次,第二轮是n-2次,第三轮是n-3次,依次类推,其实它的时间复杂度可以归结为:O(n)= n * (n - 1) / 2,其实可以将它优化成O(n) = n,加个判断如下:

    接下来就可以将我们的纸排的排序改为咱们自己实现的冒泡排序了,如下:

    这种场景下利用冒泡就是一个非常之高效的算法了。

    选择排序:

    在n是个位数量的时候进行排序除了冒泡比较快之外,还有选择排序,而它是快速排序的基础功,所以需要好好学一下,所以下面来看一下用选择排序如何来实现。

    这里以“3,2,5,8,1,9,4,6,7”这个数列进行说明,跟冒泡算法一样先来看第一轮,一点点来,它的规则是先将数组的第一个元素定死,也就是:

    此时有个index指向它:

    然后再从剩下的元素中找到一个最小元素的下标,并且index指向它,如下:

    这里可以先把代码跟着写好:

    紧接着将这个index最小的元素和数组的第一个元素进行交换,如下:

     

    好,代码跟上:

    所以这里又得要个外层循环了:

    当然这里也可以进行优化一下:

    很明显用选择排序的交互次数要比冒泡排序的交换次数要少些,但是其实它们俩在数据量在个位数时其实是差不多的,下面则把这个选择排序应用到牌的排序中:

    链式存储结构:

    它长这样:

    这次先不学它,因为东西有点多,下次再专门学习。 

  • 相关阅读:
    数据库 —— 基于 ORM 模型的 Hibernate 的使用(java)
    数据库 —— mySQL 的安装
    数据库 —— 应用程序与数据库的连接
    windows 编程 —— 子窗口类别化(Window Subclassing)
    windows 编程 —— 消息与参数(定时器、初始化消息、改变大小)
    windows 编程 —— 子窗口 与 子窗口控件
    windows 编程 —— 消息与参数(滚动条、键盘、鼠标)
    windows 编程—— 使用函数笔记
    关于计算机编程语言——编译型和解释型_2
    关于计算机编程语言——编译型和解释型【转】
  • 原文地址:https://www.cnblogs.com/webor2006/p/11408110.html
Copyright © 2011-2022 走看看