精华内容
下载资源
问答
  • 做笔记的好处和作用
    千次阅读
    2016-10-09 15:16:01

    1、函数中的作用域

    考虑如下的代码:

    function foo(a) { 
        var b = 2;
        // 一些代码
        function bar() { 
            // ...
        }
        // 更多的代码 
        var c = 3;
    }

    在这个代码片段中,foo(..) 的作用域中包含了标识符 a、b、c 和 bar。

    bar(..) 拥有自己的作用域。全局作用域也有自己的作用域气泡,它只包含了一个标识符:foo。

    由于标识符 a、b、c 和 bar 都附属于 foo(..) 的作用域,因此无法从 foo(..) 的外部 对它们进行访问。也就是说,这些标识符全都无法从全局作用域中进行访问,因此下面的代码会导致 ReferenceError 错误:

    bar(); // 失败
    console.log( a, b, c ); // 三个全都失败

    函数作用域的含义是指,属于这个函数的全部变量都可以在整个函数的范围内使用及复用(事实上在嵌套的作用域中也可以使用)。这种设计方案是非常有用的,能充分利用 JavaScript 变量可以根据需要改变值类型的“动态”特性。

    但与此同时,如果不细心处理那些可以在整个作用域范围内被访问的变量,可能会带来意想不到的问题。

    2、隐藏内部实现

    对函数的传统认知就是先声明一个函数,然后再向里面添加代码。但反过来想也可以带来一些启示:从所写的代码中挑选出一个任意的片段,然后用函数声明对它进行包装,实际上就是把这些代码“隐藏”起来了。

    实际的结果就是在这个代码片段的周围创建了一个作用域气泡,也就是说这段代码中的任何声明(变量或函数)都将绑定在这个新创建的包装函数的作用域中,而不是先前所在的作用域中。换句话说,可以把变量和函数包裹在一个函数的作用域中,然后用这个作用域来“隐藏”它们。

    为什么“隐藏”变量和函数是一个有用的技术?

    有很多原因促成了这种基于作用域的隐藏方法。它们大都是从最小特权原则中引申出来的,也叫最小授权或最小暴露原则。

    这个原则是指在软件设计中,应该最小限度地暴露必要内容,而将其他内容都“隐藏”起来,比如某个模块或对象的 API 设计。

    这个原则可以延伸到如何选择作用域来包含变量和函数。如果所有变量和函数都在全局作用域中,当然可以在所有的内部嵌套作用域中访问到它们。但这样会破坏前面提到的最小特权原则,因为可能会暴漏过多的变量或函数,而这些变量或函数本应该是私有的,正确的代码应该是可以阻止对这些变量或函数进行访问的。

    function doSomething(a) {
        b = a + doSomethingElse( a * 2 );
        console.log( b * 3 );
    }
    function doSomethingElse(a) { 
        return a - 1;
    }
    var b;
    doSomething( 2 ); // 15

    在这个代码片段中,变量 b 和函数 doSomethingElse(..) 应该是 doSomething(..) 内部具体实现的“私有”内容。给予外部作用域对 b 和 doSomethingElse(..) 的“访问权限”不仅没有必要,而且可能是“危险”的,因为它们可能被有意或无意地以非预期的方式使用, 从而导致超出了 doSomething(..) 的适用条件。更“合理”的设计会将这些私有的具体内容隐藏在 doSomething(..) 内部:

    function doSomething(a) { 
        function doSomethingElse(a) {
            return a - 1; 
        }
        var b;
        b = a + doSomethingElse( a * 2 );
        console.log( b * 3 );
    }
    doSomething( 2 ); // 15

    现在,b 和 doSomethingElse(..) 都无法从外部被访问,而只能被 doSomething(..) 所控制。功能性和最终效果都没有受影响,但是设计上将具体内容私有化了,设计良好的软件都会 依此进行实现。

    规避冲突:

    “隐藏”作用域中的变量和函数所带来的另一个好处,是可以避免同名标识符之间的冲突, 两个标识符可能具有相同的名字但用途却不一样,无意间可能造成命名冲突。冲突会导致变量的值被意外覆盖。

    1、全局命名空间

    变量冲突的一个典型例子存在于全局作用域中。当程序中加载了多个第三方库时,如果它们没有妥善地将内部私有的函数或变量隐藏起来,就会很容易引发冲突。

    这些库通常会在全局作用域中声明一个名字足够独特的变量,通常是一个对象。这个对象被用作库的命名空间,所有需要暴露给外界的功能都会成为这个对象(命名空间)的属性,而不是将自己的标识符暴漏在顶级的词法作用域中。

    2、模块管理

    另外一种避免冲突的办法和现代的模块机制很接近,就是从众多模块管理器中挑选一个来使用。使用这些工具,任何库都无需将标识符加入到全局作用域中,而是通过依赖管理器 的机制将库的标识符显式地导入到另外一个特定的作用域中。

    显而易见,这些工具并没有能够违反词法作用域规则的“神奇”功能。它们只是利用作用域的规则强制所有标识符都不能注入到共享作用域中,而是保持在私有、无冲突的作用域中,这样可以有效规避掉所有的意外冲突。

    3、函数作用域

    在任意代码片段外部添加包装函数,可以将内部的变量和函数定义“隐藏”起来,外部作用域无法访问包装函数内部的任何内容。

    var a = 2;
    function foo() { 
        var a = 3; 
        console.log( a ); // 3
    } 
    foo();
    console.log( a ); // 2

    虽然这种技术可以解决一些问题,但是它并不理想,因为会导致一些额外的问题。

    首先,必须声明一个具名函数 foo(),意味着 foo 这个名称本身“污染”了所在作用域(在这个例子中是全局作用域)。
    其次,必须显式地通过函数名(foo())调用这个函数才能运行其中的代码。

    如果函数不需要函数名(或者至少函数名可以不污染所在作用域),并且能够自动运行,这将会更加理想。

    JavaScript 提供了能够同时解决这两个问题的方案。

    (function foo(){ 
        var a = 3;
        console.log( a ); // 3 
    })();

    首先,包装函数的声明以 (function… 而不仅是以 function… 开始。尽管看上去这并不是一个很显眼的细节,但实际上却是非常重要的区别。函数会被当作函数表达式而不是一个标准的函数声明来处理。

    区分函数声明和表达式最简单的方法是看 function 关键字出现在声明中的位置(不仅仅是一行代码,而是整个声明中的位置)。如果 function 是声明中 的第一个词,那么就是一个函数声明,否则就是一个函数表达式。

    函数声明和函数表达式之间最重要的区别是它们的名称标识符将会绑定在何处。 比较一下前面两个代码片段。第一个片段中 foo 被绑定在所在作用域中,可以直接通过 foo() 来调用它。第二个片段中 foo 被绑定在函数表达式自身的函数中而不是所在作用域中。

    匿名和具名

    对于函数表达式你最熟悉的场景可能就是回调参数了,比如:

     setTimeout( function() {
        console.log("I waited 1 second!");
    }, 1000 );

    这叫作匿名函数表达式,因为 function().. 没有名称标识符。函数表达式可以是匿名的,而函数声明则不可以省略函数名——在 JavaScript 的语法中这是非法的。

    匿名函数表达式书写起来简单快捷,很多库和工具也倾向鼓励使用这种风格的代码。但是它也有几个缺点需要考虑。

    1. 匿名函数在栈追踪中不会显示出有意义的函数名,使得调试很困难。
    2. 如果没有函数名,当函数需要引用自身时只能使用已经过期的arguments.callee引用, 比如在递归中。另一个函数需要引用自身的例子,是在事件触发后事件监听器需要解绑自身。
    3. 匿名函数省略了对于代码可读性/可理解性很重要的函数名。一个描述性的名称可以让代码不言自明。

    行内函数表达式非常强大且有用——匿名和具名之间的区别并不会对这点有任何影响。给函数表达式指定一个函数名可以有效解决以上问题。始终给函数表达式命名是一个最佳实践:

    setTimeout( function timeoutHandler() { // <-- 快看,有名字了! 
        console.log( "I waited 1 second!" );
    }, 1000 );

    立即执行函数表达式

    var a = 2;
    (function foo() { 
        var a = 3;
        console.log( a ); // 3
    })();
    console.log( a ); // 2

    由于函数被包含在一对 ( ) 括号内部,因此成为了一个表达式,通过在末尾加上另外一个 ( ) 可以立即执行这个函数,比如 (function foo(){ .. })()。第一个 ( ) 将函数变成表达式,第二个 ( ) 执行了这个函数。

    这种模式很常见,几年前社区给它规定了一个术语:IIFE,代表立即执行函数表达式 (Immediately Invoked Function Expression)。

    相较于传统的 IIFE 形式,很多人都更喜欢另一个改进的形式:(function(){ .. }())。仔细观察其中的区别。第一种形式中函数表达式被包含在( )中,然后在后面用另一个()括号来调用。第二种形式中用来调用的()括号被移进了用来包装的( )括号中。

    这两种形式在功能上是一致的。选择哪个全凭个人喜好。

    IIFE 的另一个非常普遍的进阶用法是把它们当作函数调用并传递参数进去。

    var a = 2;
    (function IIFE( global ) {
        var a = 3;
        console.log( a ); // 3 
        console.log( global.a ); // 2
    })( window );
    console.log( a ); // 2

    我们将 window 对象的引用传递进去,但将参数命名为 global,因此在代码风格上对全局对象的引用变得比引用一个没有“全局”字样的变量更加清晰。当然可以从外部作用域传递任何你需要的东西,并将变量命名为任何你觉得合适的名字。这对于改进代码风格是非常有帮助的。

    这个模式的另外一个应用场景是解决 undefined 标识符的默认值被错误覆盖导致的异常(虽然不常见)。将一个参数命名为 undefined,但是在对应的位置不传入任何值,这样就可以保证在代码块中 undefined 标识符的值真的是 undefined:

    undefined = true; // 给其他代码挖了一个大坑!绝对不要这样做! 
    (function IIFE( undefined ) {
        var a;
        if (a === undefined) {
            console.log( "Undefined is safe here!" );
        }
    })();

    IIFE 还有一种变化的用途是倒置代码的运行顺序,将需要运行的函数放在第二位,在 IIFE 执行之后当作参数传递进去。

    这种模式在UMD(Universal Module Definition)项目中被广泛使用。尽管这种模式略显冗长,但有些人认为它更易理解。

    var a = 2;
    (function IIFE( def ) { 
        def( window );
    })(function def( global ) {
        var a = 3;
        console.log( a ); // 3 
        console.log( global.a ); // 2
    });

    4、块作用域

    尽管函数作用域是最常见的作用域单元,当然也是现行大多数 JavaScript 中最普遍的设计方法,但其他类型的作用域单元也是存在的,并且通过使用其他类型的作用域单元甚至可以实现维护起来更加优秀、简洁的代码。

    除 JavaScript 外的很多编程语言都支持块作用域,因此其他语言的开发者对于相关的思维方式会很熟悉,但是对于主要使用 JavaScript 的开发者来说,这个概念会很陌生。

    下面两段代码中的变量,都会污染全局作用域。

    for (var i=0; i<10; i++) { 
        console.log( i );
    }
    
    var foo = true;
    if (foo) {
        var bar = foo * 2;
        bar = something( bar ); 
        console.log( bar );
    }

    表面上看 JavaScript 并没有块作用域的相关功能。

    除非你更加深入地研究。

    with

    它不仅是一个难于理解的结构,同时也是块作用域的一个例子(块作用域的一种形式),用 with 从对象中创建出的作用域仅在 with 声明中而非外部作用域中有效。

    try/catch

    JavaScript 的 ES3 规范中规定 try/catch 的 catch 分句会创建一个块作用域,其中声明的变量仅在 catch 内部有效。

    try {
        undefined(); // 执行一个非法操作来强制制造一个异常
    }
    catch (err) {
        console.log( err ); // 能够正常执行! 
    }
    
    console.log( err ); // ReferenceError: err not found

    正如你所看到的,err 仅存在 catch 分句内部,当试图从别处引用它时会抛出错误。

    let

    ES6 引入了新的 let 关键字,提供了除 var 以外的另一种变量声明方式。

    let 关键字可以将变量绑定到所在的任意作用域中(通常是 { .. } 内部)。换句话说,let为其声明的变量隐式地了所在的块作用域。

    var foo = true;
    if (foo) {
        let bar = foo * 2;
        console.log( bar );
    }
    console.log( bar ); // ReferenceError

    但是使用 let 进行的声明不会在块作用域中进行提升。声明的代码被运行之前,声明并不“存在”。
    (提升是指声明会被视为存在于其所出现的作用域的整个范围内。)

    块作用域非常有用的原因和闭包及回收内存垃圾的回收机制相关。

    function process(data) {
        // 在这里做点有趣的事情
    }
    var someReallyBigData = { .. };
    process( someReallyBigData );
    
    var btn = document.getElementById( "my_button" );
    btn.addEventListener( "click", function click(evt) {
        console.log("button clicked");
    }, /*capturingPhase=*/false );

    click 函数的点击回调并不需要 someReallyBigData 变量。理论上这意味着当 process(..) 执 行后,在内存中占用大量空间的数据结构就可以被垃圾回收了。但是,由于 click 函数形成 了一个覆盖整个作用域的闭包,JavaScript 引擎极有可能依然保存着这个结构(取决于具体实现)。

    块作用域可以打消这种顾虑,可以让引擎清楚地知道没有必要继续保存 someReallyBigData 了:

    function process(data) {
        // 在这里做点有趣的事情
    }
    // 在这个块中定义的内容可以销毁了! 
    {
        let someReallyBigData = { .. }; 
        process( someReallyBigData );
    }
    var btn = document.getElementById( "my_button" );
    btn.addEventListener( "click", function click(evt){
        console.log("button clicked");
    }, /*capturingPhase=*/false );

    let循环

    一个 let 可以发挥优势的典型例子就是之前讨论的 for 循环。

    for (let i=0; i<10; i++) { 
        console.log( i );
    }
    console.log( i ); // ReferenceError

    const

    除了 let 以外,ES6 还引入了 const,同样可以用来创建块作用域变量,但其值是固定的 (常量)。之后任何试图修改值的操作都会引起错误。

    var foo = true;
    if (foo) {
        var a = 2;
        const b = 3; //包含在if中的块作用域常量
        a = 3; //正常!
        b = 4; //错误! 
    }
    console.log( a ); // 3
    console.log( b ); // ReferenceError!
    更多相关内容
  • 超硬核!数据结构学霸笔记,考试面试吹牛就靠它

    万次阅读 多人点赞 2021-03-26 11:11:21
    上次发操作系统笔记,很快浏览上万,这次数据结构比上次硬核的多哦,同样的会发超硬核代码,关注吧。

    上次发操作系统笔记,很快浏览上万,这次数据结构比上次硬核的多哦,同样的会发超硬核代码,关注吧。

    超硬核!操作系统学霸笔记,考试复习面试全靠它

     


     


     

    第一次笔记(复习c,课程概述)

    第一节课复习了c语言的一些知识,并简单介绍了数据结构这门课程。

     

    1、引用和函数调用:

    1.1引用:对一个数据建立一个“引用”,他的作用是为一个变量起一个别名。这是C++对C语言的一个重要补充。

    用法很简单:

    int a = 5;

    int &b = a;

    b是a别名,b与a代表的是同一个变量,占内存中同一个存储单元,具有同一地址。

    注意事项:

    1. 声明一个引用,同时必须初始化,及声明它代表哪一个变量。(作为函数参数时不需要初始化)
       
    2. 在声明一个引用后,不能再作为另一变量的引用。

         3。不能建立引用数组。

    1.2函数调用:

    其实还是通过函数来理解了一下引用

    void Myswap1(int a,int b)
    {
    	int c = a;
    	a = b;
    	b = c;
    }
    
    void Myswap2(int &a,int &b)
    {
    	int c = a;
    	a = b;
    	b = c;
    }
    
    void Myswap3(int *pa,int *pb)
    {
    	int c = *pa;
    	*pa = *pb;
    	*pb = c;
    }

    这三个函数很简单,第一个只传了值进来,不改变全局变量;而第三个也很熟悉,就是传递了地址,方便操作。依旧是“值传递”的方式,只不过传递的是变量的地址而已;那二就彻底改变了这些东西,引用作为函数参数,传入的实参就是变量,而不是数值,真正意义上的“变量传递”。

     

    2、数组和指针:

    这一块讲得比较简单,就是基本知识。

    主要内容:

    1、函数传数组就是传了个指针,这个大家都知道,所以传的时候你写arr[],里面写多少,或者不写,都是没关系的,那你后面一定要放一个变量来把数组长度传进来。

    2、还有就是,定义:int arr[5],你访问越界是不会报错的,但是逻辑上肯定就没有道理了。那用typedef int INTARR[3];访问越界,在vs上会报错,要注意。

    3、再说一下指针和数组名字到底有什么区别?这本来就是两个东西,可能大家没注意罢了。

    第一:指针可以自增,数组名不行,因为是常量啊。

    第二:地址不同,虽然名字[n],都可以这样用,但是数组名地址就是第一个元素地址。指针地址就是那个指针的地址,指针里存的才是第一个元素地址。

    第三:sizeof(),空间不一样,数组是占数组那么大空间。指针是四个字节。

    本来就是俩东西,这么多不同都是本质不同的体现。

    3、结构体:

    也是讲的基本操作,基本就是这个东西:

    typedef struct Date
    {
    	int Year;
    	int Month;
    	int Day;
    	struct Date *pDate;
    }Date, *pDate;

    1、内部无指向自己的指针才可以第一行先不起名字。

    2、内部不能定义自己的,如果能的话那空间不就无限了么。很简单的逻辑

     

    指针我不习惯,还是写Date *比较顺眼

    3、有同学没注意:访问结构体里的东西怎么访问?

    Date.这种形式,或者有指向这个节点的指针p可以p->这种形式,别写错了。

     

    4、还有就是结构体能直接这么赋值:

       Date d1 = {2018,9,11};

    我竟然不知道,以前还傻乎乎的一个一个赋值呢。

     

    5、还有,想写一下结构体有什么优点。。

    这一块可能写的就不好了,因为不是老师讲的。。

    比如学生成绩,如果不用结构体,我们一个学生可能有十几个信息,那定义变量和操作就很烦琐了,数据结构有一种松散的感觉。用一个结构体来表示更好,无论是程序的可读性还是可移植性还是可维护性,都得到提升。还有就是函数调用的时候,传入相当多的参数,又想操作或者返回,那是很麻烦的事。现在只传入一个结构体就好了,操作极其简单。总结一下就是好操作,中看中用,有机地组织了对象的属性。以修改结构体成员变量的方法代替了函数(入口参数)的重新定义。

    基本就这样吧。

    6、还有就是它了:typedef int INTARR[3];这样直接定义了一个数据类型,长度为3的数组,然后直接这样用就可以了:

    INTARR arr1;

     

    回忆完C语言储备知识,然后讲了数据结构的基本概念

     

    数据结构是一门研究非数值计算的程序设计问题中计算机的操作对象以及他们之间的关系和操作等的学科。

    数据:是对客观事物的符号表示,在计算机中指能输入到计算机中并被处理的符号总称。

    数据元素:数据的基本单位

    数据项:数据的不可分割的最小单位

    数据对象:性质相同的数据元素的集合。

    举例:动物是数据,某只动物是数据元素,猫狗是数据对象,颜色可以是数据项。

     

    数据元素之间存在某种关系,这种关系成为结构。

    四种基本结构:

    集合:除了同属一个集合无其他关系。

    线性结构:一对一的关系

    树形结构:一对多的关系

    图状结构:多对多的关系

     

    第二次笔记(基本概念,时间空间复杂度)

    今天继续说明了一些基本概念,讲解了时间空间复杂度。

    (对于概念的掌握也很重要)

     

    元素之间的关系在计算机中有两种表示方法:顺序映像和非顺序映像,由此得到两种不同的储存结构:

    顺序存储结构和链式存储结构。

     

    顺序:根据元素在存储器中的相对位置表示关系

    链式:借助指针表示关系

     

    数据类型:是一个值的集合和定义在这个值集上的一组操作的总称。

    抽象数据类型:是指一个数学模型以及定义在该模型上的一组操作。(仅仅取决于逻辑特性,与其在计算机内部如何表示和实现无关)

     

    定义抽象数据类型的一种格式:

    ADT name{

    数据对象:<>

    数据关系:<>

    基本操作:<>

    }ADT name

     

    算法:是对特定问题求解步骤的一种描述。

    算法五个特性:

    1. 有穷性:有穷的时间内完成,或者可以说是可接受的时间完成
    2. 确定性:对于相同的输入只能得到相同的输出
    3. 可行性:描述的操作都可以执行基本操作有限次来实现
    4. 输入:零个或多个输入。取自于某个特定对象的集合
    5. 输出:一个或多个输出

     

    设计要求:正确性、可读性、健壮性、效率与低存储量需求。

    执行频度概念:是指通过统计算法的每一条指令的执行次数得到的。

    执行频度=算法中每一条语句执行次数的和

    一般认定每条语句执行一次所需时间为单位时间(常数时间)O(1)

     

    几个小知识和小问题:

    1)循环执行次数n+1次,不是n次。第一次执行i=1和判断i<=n以后执行n次判断和i++。所以该语句执行了n+1次。在他的控制下,循环体执行了n次。

    2)四个并列程序段:分别为O(N),O(N^2),O(N^3),O(N*logN),整个程序时间复杂度为O(N^3),因为随着N的增长,其它项都会忽略

    3)算法分析的目的是分析算法的效率以求改进

    4)对算法分析的前提是算法必须正确

    5)原地工作指的不是不需要空间,而是空间复杂度O(1),可能会需要有限几个变量。

    实现统一功能两种算法:时间O(2^N),O(N^10),假设计算机可以运行10^7秒,每秒可执行基本操作10^5次,问可解问题规模各为多少?选哪个更为合适?

    计算机一共可执行10^7*10^5=10^12次

    第一种:n=log2,(10^12)=12log(2,10)

    第二种:n=(10^12)^0.1

    显然1更适用。

    虽然一般情况多项式算法比指数阶更优

     

    时间空间复杂度概述

     

    找个时间写一写时间复杂度和一些问题分类,也普及一下这方面知识。

    如何衡量一个算法好坏

    很显然,最重要的两个指标:需要多久可以解决问题、解决问题耗费了多少资源

    那我们首先说第一个问题,要多长时间来解决某个问题。那我们可以在电脑上真实的测试一下嘛,多种方法比一比,用时最少的就是最优的啦。

    但是没必要,我们可以通过分析计算来确定一个方法的好坏,用O()表示,括号内填入N、1,等式子。

    这到底是什么意思呢?

    简单来说,就是这个方法,时间随着数据规模变化而增加的快慢。时间可以当成Y,数据规模是X,y=f(x),就这样而已。但是f(x)不是准确的,只是一个大致关系,y=10x,我们也视作x,因为他的增长速度还是n级别的。现在就可以理解了:一般O(N)就是对每个对象访问优先次数而已。请注意:O(1)它不是每个元素访问一次,而是Y=1的感觉,y不随x变化而变化,数据多大它的时间是不变的,有限的常数操作即可完成。

    那我们就引入正规概念:

    时间复杂度是同一问题可用不同算法解决,而一个算法的质量优劣将影响到算法乃至程序的效率。算法分析的目的在于选择合适算法和改进算法。

    计算机科学中,算法的时间复杂度是一个函数,它定性描述了该算法的运行时间。这是一个关于代表算法输入值的字符串的长度的函数。时间复杂度常用大O符号表述,不包括这个函数的低阶项和首项系数。使用这种方式时,时间复杂度可被称为是渐近的,它考察当输入值大小趋近无穷时的情况。

    注意:文中提到:不包括这个函数的低阶项和首项系数。什么意思呢?就是说10n,100n,哪怕1000000000n,还是算做O(N),而低阶项是什么意思?不知大家有没有学高等数学1,里面有最高阶无穷大,就是这个意思。举个例子。比如y=n*n*n+n*n+n+1000

    就算做o(n*n*n),因为增长速率最大,N*N及其它项增长速率慢,是低阶无穷大,n无限大时,忽略不计。

     

    那接着写:o(n*n*n)的算法一定不如o(n)的算法吗?也不一定,因为之前说了,时间复杂度忽略了系数,什么意思?o(n)可以是10000000n,当n很小的时候,前者明显占优。

    所以算法要视实际情况而定。

    算法的时间 复杂度常见的有:
    常数阶 O(1),对数阶 O(log n),线性阶 O(n),
    线性对数阶 O(nlog n),平方阶 O(n^2),立方阶 O(n^3),…,
    k 次方阶O(n^k),指数阶 O(2^n),阶乘阶 O(n!)。

    常见的算法的时间 复杂度之间的关系为:
           O(1)<O(log n)<O(n)<O(nlog n)<O(n^2)<O(2^n)<O(n!)<O(n^n) 

     

    我们在竞赛当中,看见一道题,第一件事就应该是根据数据量估计时间复杂度。

    计算机计算速度可以视作10^9,如果数据量是10000,你的算法是O(N*N),那就很玄,10000*10000=10000 0000,别忘了还有常数项,这种算法只有操作比较简单才可能通过。你可以想一想O(nlog n)的算法一般就比较稳了。那数据量1000,一般O(N*N)就差不多了,数据量更小就可以用复杂度更高的算法。大概就这样估算。

     

    当 n 很大时,指数阶算法和多项式阶算法在所需时间上非常
    悬殊。因此,只要有人能将现有指数阶算法中的任何一个算法化
    简为多项式阶算法,那就取得了一个伟大的成就。

    体会一下:

     

    空间复杂度也是一样,用来描述占空间的多少。

    注意时间空间都不能炸。

    所以才发明了那么多算法。

    符上排序算法的时间空间表,体会一下:

     


    排序博客:加深对时间空间复杂度理解

    https://blog.csdn.net/hebtu666/article/details/81434236

     

     

     

    引入:算法优化

     

    想写一系列文章,总结一些题目,看看解决问题、优化方法的过程到底是什么样子的。

     

    系列问题一:斐波那契数列问题

    在数学上,斐波纳契数列以如下被以递归的方法定义:F(0)=0,F(1)=1, F(n)=F(n-1)+F(n-2)(n>=2,n∈N*)根据定义,前十项为1, 1, 2, 3, 5, 8, 13, 21, 34, 55

    问题一:

    给定一个正整数n,求出斐波那契数列第n项(这时n较小)

    解法一:最笨,效率最低,暴力递归,完全是抄定义。请看代码

    def f0(n):
        if n==1 or n==2:
            return 1
        return f(n-1)+f(n-2)

     

    分析一下,为什么说递归效率很低呢?咱们来试着运行一下就知道了

     

    比如想求f(10),计算机里怎么运行的?

     

    每次要计算的函数量都是指数型增长,时间复杂度O(2^(N/2))<=T(N)<=O(2^N),效率很低。效率低的原因是,进行了大量重复计算,比如图中的f(8),f(7).....等等,你会发现f(8)其实早就算过了,但是你后来又要算一遍。

    如果我们把计算的结果全都保存下来,按照一定的顺序推出n项,就可以提升效率, 斐波那契(所有的一维)的顺序已经很明显了,就是依次往下求。。

    优化1

    def f1(n):
        if n==1 or n==2:
            return 1
        l=[0]*n
        l[0],l[1]=1,1
        for i in range(2,n):
            l[i]=l[i-1]+l[i-2]
        return l[-1]

     

    时间复杂度o(n),依次往下打表就行,空间o(n).

    继续优化:既然只求第n项,而斐波那契又严格依赖前两项,那我们何必记录那么多值呢?记录前两项就行了

     

    优化2

    def f2(n):
        a,b=1,1
        for i in range(n-1):
            a,b=b,a+b
        return a

    此次优化做到了时间o(n),空间o(1)

    附:这道题掌握到这里就可以了,但是其实有时间o(log2n)的方法

     

    优化三:

    学习过线性代数的同学们能够理解:

     

    结合快速幂算法,我们可以在o(log2n)内求出某个对象的n次幂。(在其它专题详细讲解)

    注意:只有递归式不变,才可以用矩阵+快速幂的方法。

    注:优化三可能只有在面试上或竞赛中用,当然,快速幂还是要掌握的。

     

    经过这个最简单的斐波那契,大家有没有一点感觉了?

    好,我们继续往下走

    (补充:pat、蓝桥杯原题,让求到一万多位,结果模一个数。

    可以每个结果都对这个数取模,否则爆内存,另:对于有多组输入并且所求结果类似的题,可以先求出所有结果存起来,然后直接接受每一个元素,在表中查找相应答案)

     

    问题二:

    一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

    依旧是找递推关系,分析:跳一阶,就一种方法,跳两阶,它可以一次跳两个,也可以一个一个跳,所以有两种,三个及三个以上,假设为n阶,青蛙可以是跳一阶来到这里,或者跳两阶来到这里,只有这两种方法。它跳一阶来到这里,说明它上一次跳到n-1阶,同理,它也可以从n-2跳过来,f(n)为跳到n的方法数,所以,f(n)=f(n-1)+f(n-2)

    和上题的优化二类似。

    因为递推式没变过,所以可以用优化三

     

    问题三:

    我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?提示,大矩形看做是长度吧

    请读者自己先思考一下吧。。。仔细看题。。仔细思考

    如果n是1,只有一种,竖着放呗;n是2,两种:

    n等于3,三种:

     

    题意应该理解了吧?

    读到这里,你们应该能很快想到,依旧是斐波那契式递归啊。

    对于n>=3:怎么能覆盖到三?只有两种办法,从n-1的地方竖着放了一块,或者从n-2的位置横着放了两块呗。

    和问题二的代码都不用变。

     

    问题四:

    给定一个由0-9组成的字符串,1可以转化成A,2可以转化成B。依此类推。。25可以转化成Y,26可以转化成z,给一个字符串,返回能转化的字母串的有几种?

    比如:123,可以转化成

    1 2 3变成ABC,

    12 3变成LC,

    1 23变成AW,三种,返回三,

    99999,就一种:iiiii,返回一。

    分析:求i位置及之前字符能转化多少种。

    两种转化方法,一,字符i自己转换成自己对应的字母,二,和前面那个数组成两位数,然后转换成对应的字母

    假设遍历到i位置,判断i-1位置和i位置组成的两位数是否大于26,大于就没有第二种方法,f(i)=f(i-1),想反,等于f(i-1)+f(i-2)

    注意:递归式不确定,不能用矩阵快速幂

     

    (这几个题放到这里就是为了锻炼找递归关系的能力,不要只会那个烂大街的斐波那契)

     

    '''
    斐波那契问题:
    斐波纳契数列以如下被以递归的方法定义:
    F(1)=1
    F(2)=1
    F(n)=F(n-1)+F(n-2)(n>=2,n∈N*)
    '''
    #暴力抄定义,过多重复计算
    def f0(n):
        if n==1 or n==2:
            return 1
        return f(n-1)+f(n-2)
    #记录结果后依次递推的优化,时间O(N)
    def f1(n):
        if n==1 or n==2:
            return 1
        l=[0]*n
        l[0],l[1]=1,1
        for i in range(2,n):
            l[i]=l[i-1]+l[i-2]
        return l[-1]
    #既然严格依赖前两项,不必记录每一个结果,优化空间到O(1)
    def f2(n):
        a,b=1,1
        for i in range(n-1):
            a,b=b,a+b
        return a

    第三次笔记(线性表结构和顺序表示)

     

    这节课介绍了线性表结构和顺序表示的一部分内容。

    操作太多,而且书上有,就不一一介绍分析了。

    线性表定义:n个数据元素的有限序列。

    特点:

    1. 存在唯一一个称作“第一个”的元素。
    2. 存在唯一一个称作“最后一个”的元素
    3. 除最后一个元素外,集合中每一个元素都只有一个直接前趋
    4. 除最后一个元素外,集合中每一个元素都只有一个直接后继

    注意1、2条:证明循环的序列不是线性表

     

    注意:

    1)线性表从1开始,线性表第一个元素对应到数组中下标是0.

    2)函数通过引用对线性表的元素进行修改即可

    3)数组比较特别,它即可视为逻辑结构,又可视为存储结构。

    4)每一个表元素都是不可再分的原子数据。一维数组可以视为线性表,二维数组不可以,在逻辑上它最多可以有两个直接前趋和直接后继。

    5)线性表具有逻辑上的顺序性,在序列中各元素有其先后次序,各个数据元素在线性表中的逻辑位置只取决于序号。

     

    顺序表:是线性表的循序储存结构,以物理位置表示逻辑关系,任意元素可以随机存取。用一组地址连续的存储单元依次存储线性表中的元素。逻辑顺序和物理顺序是一致的。可以顺序访问,也可随机访问。

    顺序表存储:

    这些定式还是很重要的,比如define typedef等,真正实现时最好就这样写,不要自己规定个长度和数据类型,这样以后好维护、修改。

    静态存储分配:

    #define maxSize 100//显式定义表长

    Typedef int DataType;//定义数据类型

    Typedef struct{

    DataType data[maxSize];//静态分配存储表元素向量

    Int n;//实际表中个数

    }SeqList;

     

    动态存储分配:

    #define maxSize 100//长度初始定义

    Typedef int DataType;//定义数据类型

    Typedef struct{

    DataType *data;//动态分配数组指针

    Int maxSize,n;//最大容量和当前个数

    }SeqList;

     

    初始动态分配:

    Data=(DataType *)malloc(sizeof(DataType)* initSize);

    C++:data=new DataType[initSize];

    maxSize=initSize;n=0;

    动态分配存储,向量的存储空间是在程序执行过程中通过动态存储分配来获取的。空间满了就另外分配一块新的更大的空间,用来代替原来的存储空间,从而达到扩充的目的。

     

    插入:需要查找,移动元素,概率上1,2,3....n,平均O(N)

    删除:同样需要移动元素。填充被空出来的存储单元。

    在等概率下平均移动次数分别为n/2,(n-1)/2

     插入注意事项:

    1. 需要判断是否已满
    2. 要从后向前移动,否则会冲掉元素

    删除注意事项:

    1. 需要先判断是否已空
    2. 需要把后方元素前移,要从前向后。

     

    注意:线性表的顺序存储借用了一维数组,但是二者不同:

    1. 一维数组各非空结点可以不相继存放,但顺序表是相继存放的
    2. 顺序表长度是可变的,一维数组长度是确定的,一旦分配就不可变
    3. 一维数组只能按下标存取元素,顺序表可以有线性表的所有操作。

     

     

    第四次笔记(链表概述)

     

    介绍了链表和基本操作

    用一组物理位置任意的存储单元来存放线性表的数据元素。 这组存储单元既可以是连续的,也可以是不连续的,甚至是零散分布在内存中的任意位置上的。因此,链表中元素的逻辑次序和 物理次序不一定相同。

     

    定义:

    typedef  struct  Lnode{  
            //声明结点的类型和指向结点的指针类型  
            ElemType         data;    //数据元素的类型 
            struct   Lnode  *next;   //指示结点地址的指针  
    }Lnode, *LinkList;  
    struct Student
    { char num[8];   //数据域
      char name[8];  //数据域
      int score;          //数据域
      struct Student *next;  // next 既是 struct Student 
                                           // 类型中的一个成员,又指 
                                           // 向 struct Student 类型的数据。 
    }Stu_1, Stu_2, Stu_3, *LL;  
    

    头结点:在单链表的第一个结点之前人为地附设的一个结点。

    带头结点操作会方便很多。

    带和不带的我都写过了

    下面列出我见过的一些好题

    1、

    线性表的每个结点只能是一个简单类型,而链表的每个结点可以是一个复杂类型。

    • 正确
    • 错误

     

    错,线性表是逻辑结构概念,可以顺序存储或链式储,与元素数据类型无关。链表就是线性表的一种  前后矛盾

     

    2、

    若某线性表中最常用的操作是在最后一个元素之后插入一个元素和删除第一个元素,则采用(    )存储方式最节省运算时间。

    • 单链表
    • 仅有头指针的单循环链表
    • 双链表
    • 仅有尾指针的单循环链表

      对于A,B,C要想在尾端插入结点,需要遍历整个链表。

      对于D,要插入结点,只要改变一下指针即可,要删除头结点,只要删除指针.next的元素即可。

     

    3、注意:线性表是具有n个数据元素的有限序列,而不是数据项

     

    4、

    以下关于单向链表说法正确的是

    • 如果两个单向链表相交,那他们的尾结点一定相同
    • 快慢指针是判断一个单向链表有没有环的一种方法
    • 有环的单向链表跟无环的单向链表不可能相交
    • 如果两个单向链表相交,那这两个链表都一定不存在环

    自己多画画想想就明白了,关于快慢指针我以后会写总结。

     

    5、

    链接线性表是顺序存取的线性表 。 ( )

    • 正确
    • 错误

    链接线性表可以理解为链表
    线性表分为顺序表和链表
    顺序表是顺序存储结构、随机存取结构
    链表是随机存储结构、顺序存取结构

     

    6、

    typedef struct node_s{
        int item;
        struct node_s* next;
    }node_t;
    node_t* reverse_list(node_t* head)
    {
        node_t* n=head;
        head=NULL;
        while(n){
        _________               
        }
        return head;
     }

    空白处填入以下哪项能实现该函数的功能?

    • node_t* m=head; head=n; head->next=m; n=n->next;
    • node_t* m=n; n=n->next; m->next=head; head=m;
    • node_t* m=n->next; n->next=head; n=m; head=n;
    • head=n->next; head->next=n; n=n->next;


     

    代码的功能是要实现链表的反转。为了方便阐述,每个结点用①②③④⑤⑥等来标示。

    在执行while(n)循环之前,有两句代码:

    node_t* n=head;

    head=NULL;

    这两行代码的中:第一句的作用是用一个临时变量n来保存结点①,第二句是把head修改为NULL。

    然后就开始遍历了,我们看到while循环里的那四句代码:

    node_t* m=n; 
    n=n->next; 
    m->next=head; 
    head=m;
    

    先看前两句,用m来保存n,然后让n指向n的下一个结点,之所以复制 n 给 m ,是因为 n 的作用其实是  控制while循环次数  的作用,每循环一次它就要被修改为指向下一个结点。

    再看后两句,变量head在这里像是一个临时变量,后两句让 m 指向了 head,然后 head 等于 m。

     

    7、

    若某表最常用的操作是在最后一个结点之后插入一个节点或删除最后一二个结点,则采用()省运算时间。

    • 单链表
    • 双链表
    • 单循环链表
    • 带头结点的双循环链表

    D

    带头结点的双向循环链表,头结点的前驱即可找到最后一个结点,可以快速插入,再向前可以找到最后一二个结点快速删除

    单链表找到链表尾部需要扫描整个链表

    双链表找到链表尾部也需要扫描整个链表

    单循环链表只有单向指针,找到链表尾部也需要扫描整个链表

     

    8、

    单链表的存储密度(  )

    • 大于1
    • 等于1
    • 小于1
    • 不能确定

    全麦白

    存储密度 = 数据项所占空间 / 结点所占空间

     

    9、完成在双循环链表结点p之后插入s的操作是

    • s->prior=p; s->next=p->next; p->next->prior=s ; p->next=s;

     

    10、用不带头结点的单链表存储队列,其队头指针指向队头结点,队尾指针指向队尾结点,则在进行出队操作时()

    • 仅修改队头指针
    • 仅修改队尾指针
    • 队头、队尾指针都可能要修改
    • 队头、队尾指针都要修改

     

    当只有一个元素,出队列时,要将队头和队尾,指向-1.所以说队头和队尾都需要修改

     

     

    链表刷了几百道吧,好题还有很多,以后接着更新

     

     

    第六次笔记(链表选讲/静态链表)

     

    本节课介绍了单链表的操作实现细节,介绍了静态链表。

     

    链表带头的作用:对链表进行操作时,可以对空表、非空表的情况以及 对首元结点进行统一处理,编程更方便。

    下面给出带头的单链表实现思路:

     

    按下标查找:

    判断非法输入,当 1 < =i <= n 时,i 的值是合法的。

    p = L -> next; j = 1;

    while ( p && j < i ) {  p = p ->next; ++j; }

    return 

     

    按值查找:

     p = L1 -> next;

     while ( p && p ->data!=key)          p = p -> next;

    return;

     

    插入:

    判断

    查找

    创建

    插入

     

    删除:

    查找

    删除

    释放内存

     

    静态链表

    对于线性链表,也可用一维数组来进行描述。这种描述方法便于在没有指针类型的高级程序设计语言中使用链表结构。

    这种存储结构,仍需要预先分配一个较大的空间,但在作为线性表的插入和删除操作时不需移动元素,仅需修改指针,故仍具有链式存储结构的主要优点。

     

    表示:

    #define MAXSIZE 1000      / /链表的最大长度

    typedef  struct{      

        ElemType data;        

        int cur;

    }component,  SLinkList[MAXSIZE];

     

    过程:

     

     

    顺序存储线性表实现

     

    在计算机中用一组地址连续的存储单元依次存储线性表的各个数据元素,称作线性表的顺序存储结构。

     

    顺序存储结构的主要优点是节省存储空间,因为分配给数据的存储单元全用存放结点的数据(不考虑c/c++语言中数组需指定大小的情况),结点之间的逻辑关系没有占用额外的存储空间。采用这种方法时,可实现对结点的随机存取,即每一个结点对应一个序号,由该序号可以直接计算出来结点的存储地址。但顺序存储方法的主要缺点是不便于修改,对结点的插入、删除运算时,可能要移动一系列的结点。

    优点:随机存取表中元素。缺点:插入和删除操作需要移动元素。

     

    线性表中数据元素之间的关系是一对一的关系,即除了第一个和最后一个数据元素之外,其它数据元素都是首尾相接的(注意,这句话只适用大部分线性表,而不是全部。比如,循环链表逻辑层次上也是一种线性表(存储层次上属于链式存储),但是把最后一个数据元素的尾指针指向了首位结点)。

    给出两种基本实现:

    /*
    静态顺序存储线性表的基本实现
    */
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #define LIST_INITSIZE 100
    #define ElemType int
    #define Status int
    #define OK     1
    #define ERROR  0
    
    typedef struct
    {
    	ElemType elem[LIST_INITSIZE];
    	int length;
    }SqList;
    
    //函数介绍
    Status InitList(SqList *L); //初始化
    Status ListInsert(SqList *L, int i,ElemType e);//插入
    Status ListDelete(SqList *L,int i,ElemType *e);//删除
    void ListPrint(SqList L);//输出打印
    void DisCreat(SqList A,SqList *B,SqList *C);//拆分(按正负),也可以根据需求改
    //虽然思想略简单,但是要写的没有错误,还是需要锻炼coding能力的
    
    Status InitList(SqList *L)
    {
        L->length = 0;//长度为0
        return OK;
    }
    
    Status ListInsert(SqList *L, int i,ElemType e)
    {
        int j;
        if(i<1 || i>L->length+1)
            return ERROR;//判断非法输入
        if(L->length == LIST_INITSIZE)//判满
        {
            printf("表已满");//提示
            return ERROR;//返回失败
        }
        for(j = L->length;j > i-1;j--)//从后往前覆盖,注意i是从1开始
            L->elem[j] = L->elem[j-1];
        L->elem[i-1] = e;//在留出的位置赋值
        (L->length)++;//表长加1
        return OK;//反回成功
    }
    
    Status ListDelete(SqList *L,int i,ElemType *e)
    {
        int j;
        if(i<1 || i>L->length)//非法输入/表空
            return ERROR;
        *e = L->elem[i-1];//为了返回值
        for(j = i-1;j <= L->length;j++)//从前往后覆盖
            L->elem[j] = L->elem[j+1];
        (L->length)--;//长度减1
        return OK;//返回删除值
    }
    
    void ListPrint(SqList L)
    {
        int i;
        for(i = 0;i < L.length;i++)
            printf("%d ",L.elem[i]);
        printf("\n");//为了美观
    }
    
    void DisCreat(SqList A,SqList *B,SqList *C)
    {
        int i;
        for(i = 0;i < A.length;i++)//依次遍历A中元素
        {
            if(A.elem[i]<0)//判断
                ListInsert(B,B->length+1,A.elem[i]);//直接调用插入函数实现尾插
            else
                ListInsert(C,C->length+1,A.elem[i]);
        }
    }
    
    int main(void)
    {
        //复制的
    	SqList L;
    	SqList B, C;
    	int i;
    	ElemType e;
    	ElemType data[9] = {11,-22,33,-3,-88,21,77,0,-9};
    	InitList(&L);
    	InitList(&B);
    	InitList(&C);
    	for (i = 1; i <= 9; i++)
    		ListInsert(&L,i,data[i-1]);
        printf("插入完成后L = : ");
    	ListPrint(L);
        ListDelete(&L,1,&e);
    	printf("删除第1个后L = : ");
    	ListPrint(L);
        DisCreat(L , &B, &C);
    	printf("拆分L后B = : ");
    	ListPrint(B);
    	printf("拆分L后C = : ");
    	ListPrint(C);
    	printf("拆分L后L = : ");
    	ListPrint(L);
    }

    静态:长度固定

    动态:不够存放可以加空间(搬家)

     

    /*
    子任务名任务:1_2 动态顺序存储线性表的基本实现
    */
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #define LIST_INIT_SIZE 100
    #define LISTINCREMENT 10
    #define Status int
    #define OVERFLOW -1
    #define OK 1
    #define ERROR 0
    #define ElemType int
    
    typedef struct
    {
    	ElemType * elem;
    	int length;
    	int listsize;
    }SqList;
    //函数介绍
    Status InitList(SqList *L); //初始化
    Status ListInsert(SqList *L, int i,ElemType e);//插入
    Status ListDelete(SqList *L,int i,ElemType *e);//删除
    void ListPrint(SqList L);//输出打印
    void DeleteMin(SqList *L);//删除最小
    
    Status InitList(SqList *L)
    {
        L->elem = (ElemType *)malloc(LIST_INIT_SIZE*sizeof(ElemType));//申请100空间
    	if(!L->elem)//申请失败
    		return ERROR;
    	L->length = 0;//长度0
    	L->listsize = LIST_INIT_SIZE;//容量100
    	return OK;//申请成功
    }
    
    Status ListInsert(SqList *L,int i,ElemType e)
    {
        int j;
        ElemType *newbase;
        if(i<1 || i>L->length+1)
            return ERROR;//非法输入
            
        if(L->length >= L->listsize)//存满了,需要更大空间
        {
            newbase = (ElemType*)realloc(L->elem,(L->listsize+LISTINCREMENT)*sizeof(ElemType));//大10的空间
            if(!newbase)//申请失败
                return ERROR;
            L->elem = newbase;//调指针
            L->listsize+= LISTINCREMENT;//新容量
        }
        
        for(j=L->length;j>i-1;j--)//从后往前覆盖
            L->elem[j] = L->elem[j-1];
        L->elem[i-1] = e;//在留出的位置赋值
        L->length++;//长度+1
        return OK;
    }
    
    Status ListDelete(SqList *L,int i,ElemType *e)
    {
        int j;
        if(i<1 || i>L->length)//非法输入/表空
            return ERROR;
        *e = L->elem[i-1];//为了返回值
        for(j = i-1;j <= L->length;j++)//从前往后覆盖
            L->elem[j] = L->elem[j+1];
        (L->length)--;//长度减1
        return OK;//返回删除值
    }
    
    void ListPrint(SqList L)
    {
        int i;
        for(i=0;i<L.length;i++)
            printf("%d ",L.elem[i]);
        printf("\n");//为了美观
    }
    
    void DeleteMin(SqList *L)
    {
        //表空在Listdelete函数里判断
        int i;
        int j=0;//最小值下标
        ElemType *e;
        for(i=0;i<L->length;i++)//寻找最小
        {
            if(L->elem[i] < L->elem[j])
                j=i;
        }
        ListDelete(L,j+1,&e);//调用删除,注意j要+1
    }
    
    int main(void)
    {
    	SqList L;
    	int i;
    	ElemType e;
    	ElemType data[9] = {11,-22,-33,3,-88,21,77,0,-9};
    	InitList(&L);
    	for (i = 1; i <= 9; i++)
    	{
    		ListInsert(&L,i,data[i-1]);
    	}
    	printf("插入完成后 L = : ");
    	ListPrint(L);
        ListDelete(&L, 2, &e);
    	printf("删除第 2 个后L = : ");
    	ListPrint(L);
        DeleteMin(&L);
    	printf("删除L中最小值后L = : ");
    	ListPrint(L);
    	DeleteMin(&L);
    	printf("删除L中最小值后L = : ");
    	ListPrint(L);
    	DeleteMin(&L);
    	printf("删除L中最小值后L = : ");
    	ListPrint(L);
    }

    单链表,不带头实现

    链表是一种物理存储单元上非连续、非顺序的存储结构数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。

    使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。链表最明显的好处就是,常规数组排列关联项目的方式可能不同于这些数据项目在记忆体磁盘上顺序,数据的存取往往要在不同的排列顺序中转换。链表允许插入和移除表上任意位置上的节点,但是不允许随机存取。链表有很多种不同的类型:单向链表双向链表以及循环链表

     

    下面给出不带头的单链表标准实现:

    定义节点:

    typedef struct node 
    { 
        int data;
        struct node * next;
    }Node;

    尾插:

    void pushBackList(Node ** list, int data) 
    { 
        Node * head = *list;
        Node * newNode = (Node *)malloc(sizeof(Node));//申请空间
        newNode->data = data; newNode->next = NULL;
        if(*list == NULL)//为空
            *list = newNode;
        else//非空
        {
            while(head ->next != NULL)
                head = head->next;
            head->next = newNode;
        }
    }
    
    

    插入:

    int insertList(Node ** list, int index, int data) 
    {
        int n;
        int size = sizeList(*list); 
        Node * head = *list; 
        Node * newNode, * temp;
        if(index<0 || index>size) return 0;//非法
        newNode = (Node *)malloc(sizeof(Node)); //创建新节点
        newNode->data = data; 
        newNode->next = NULL;
        if(index == 0) //头插
        {
            newNode->next = head; 
            *list = newNode; 
            return 1; 
        }
        for(n=1; n<index; n++) //非头插
            head = head->next;
        if(index != size) 
            newNode->next = head->next; 
        //链表尾部next不需指定
        head->next = newNode; 
        return 1;
    }
    

    按值删除:

    void deleteList(Node ** list, int data) 
    { 
        Node * head = *list; Node * temp; 
        while(head->next!=NULL) 
        { 
            if(head->next->data != data) 
            { 
                head=head->next; 
                continue; 
            } 
            temp = head->next;
            if(head->next->next == NULL) //尾节点删除
                head->next = NULL; 
            else 
                head->next = temp->next; 
            free(temp);
        }    
        head = *list; 
        if(head->data == data) //头结点删除
        { 
            temp = head; 
            *list = head->next; 
            head = head->next; 
            free(temp); 
        }
    }
    

    打印:

    void printList(Node * head) 
    { 
        Node * temp = head; 
        for(; temp != NULL; temp=temp->next) 
            printf("%d ", temp->data); 
        printf("\n"); 
    }

    清空:

    void freeList(Node ** list) 
    { 
        Node * head = *list; 
        Node * temp = NULL; 
        while(head != NULL) //依次释放
        { 
            temp = head; 
            head = head->next; 
            free(temp); 
        } 
        *list = NULL; //置空
    }

    别的也没啥了,都是基本操作

    有些代码要分情况,很麻烦,可读性较强吧

    看我压缩代码:https://blog.csdn.net/hebtu666/article/details/81261043

     

    双链表带头实现

    以前写的不带头的单链表实现,当时也啥也没学,好多东西不知道,加上一心想压缩代码,减少情况,所以写得不太好。

    请教了老师,首先是命名问题和代码紧凑性等的改进。还有可读性方面的改进,多写了一些注释。并且因为带头的比较好写,好操作,所以标准写法也不是很长,繁琐。

     

     

    下面贴代码

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    typedef struct node{
        int key;//数据
        struct node * prev;//前驱
        struct node * next;//后继
    }Node;

    初始化(带头) 

    Node * list;
    //初始化,这里·我们list不再是NULL,而是指向了一个节点
    //这个改进方便了很多操作,也不用借助二重指针把list和next统一表示了
    
    void init(Node * list)//初始化
    {
        list = (Node *)malloc(sizeof(Node));
        list->next = NULL;
        list->prev = NULL;
    }

    查找(不用再判断一下空不空)

    Node * find(int key,Node * list)
    {
        Node * head = list->next;//从头节点后面开始找
        while(head != NULL && head->key != key)//找到或空跳出
            head = head->next;
        return head;
    }

    打印

    void printList(Node * list)//打印
    {
        Node * temp = list->next;头节点下一个开始
        while(temp != NULL)
        {
            printf("%d ",temp->key);
            temp = temp->next;
        }
        printf("\n");
    }

    删除指定结点

    void delete(Node * list)//删除指定结点
    {
        list->prev->next = list->next;前面后面指针改了,再free自己即可
        list->next->prev = list->prev;
        free(list);
    }

    配合一下删除:

    void deleteKey(int key,Node * list)
    {
        delete(find(key,list));
    }

    头插:

    void insertHead(int key,Node * list)//头插
    {
        Node * newNode = (Node *)malloc(sizeof(Node));//初始化
        newNode->key = key;
        newNode->next = list->next;//赋值后继
        if(list->next != NULL)//如果有后继,赋值后继的前指针为新结点
            list->next->prev = newNode;
        list->next = newNode;//改头
        newNode->prev = list;//赋值新结点前指针
    }

    按下标插入

    单链表都写了,我就不写长度函数和判断非法了,用的时候注意吧。

    void insert(int key,Node * list,int index)
    {
        Node * head=list;//最后指到要插位置的前一个即可
        Node * newNode = (Node *)malloc(sizeof(Node));//初始化
        newNode->key = key;
        while(index--)
            head = head->next;
        newNode->next = head->next;//修改指针
        newNode->prev = head;
        head->next = newNode;
    }

    指定某值后插入不写了,和这个修改指针逻辑一样,再传一个find配合一下就行了。

     

    然后简单测试

    int main()
    {
        Node * list = NULL;
        init(list);
        insertHead(1,list);
        insertHead(2,list);
        insertHead(3,list);
        printList(list);
        deleteKey(2,list);
        printList(list);
        insert(10,list,0);
        printList(list);
    }

     

    第七次笔记(栈/队列)

     

    介绍栈和队列基本概念和用法。

     

    设输入序列1、2、3、4,则下述序列中( )不可能是出栈序列。【中科院中国科技大学2005】

    A. 1、2、3、4                      B. 4、 3、2、1

    C. 1、3、4、2                      D.4、1、2、3

    选D

    我是一个个模拟来做的。

     

    描述栈的基本型性质:

    1、集合性:栈是由若干个元素集合而成,没有元素(空集)成为空栈。

    2、线性:除栈顶和栈底之外,任意元素均有唯一前趋和后继。

    3、运算受限:只在一端插入删除的线性表即为栈

     

    顺序存储和顺序存取:顺序存取是只能逐个存或取结构中的元素,例如栈。顺序存储是利用一个连续的空间相继存放,例如栈可基于一维数组存放元素。

     

    一个较早入栈的元素能否在后面元素之前出栈?如果后面元素压在它上面,就不可以了。如果后面元素未压入,它可以弹出。在其他元素前面。

     

     

    栈与递归:

      当在一个函数的运行期间调用另一个函数时,在运行 该被调用函数之前,需先完成三件事:  将实参等传递给被调用函数,保存返回地址(入栈);  为被调用函数的局部变量分配存储区;    将控制转移到被调用函数的入口。  

    从被调用函数返回调用函数之前,应该完成:  保存被调函数的计算结果;  释放被调函数的数据区;  按被调函数保存的返回地址(出栈)将控制转移到调        用函数。

    多个函数嵌套调用的规则是:后调用先返回。

     此时的内存管理实行“栈式管理”

     

    队列:

            在多用户计算机系统中,各个用户需要使用 CPU 运行自己的程序,它们分别向操作系统提出使用 CPU 的请求,操作系统按照每个请求在时间上的先后顺序, 将其排成一个队列,每次把CPU分配给队头用户使用, 当相应的程序运行结束,则令其出队,再把CPU分配 给新的队头用户,直到所有用户任务处理完毕。

     

    以主机和打印机为例来说明,主机输出数据给打印 机打印,主机输出数据的速度比打印机打印的速度要快 得多,若直接把输出的数据送给打印机打印,由于速度 不匹配,显然不行。解决的方法是设置一个打印数据缓 冲区,主机把要打印的数据依此写到这个缓冲区中,写 满后就暂停输出,继而去做其它的事情,打印机就从缓 冲区中按照先进先出的原则依次取出数据并打印,打印 完后再向主机发出请求,主机接到请求后再向缓冲区写 入打印数据,这样利用队列既保证了打印数据的正确, 又使主机提高了效率。

     

    双端队列:

    某队列允许在其两端进行入队操作,但仅允许在一端进行出队操作,若元素a,b,c,d,e依次入队列后,再进行出队操作,则不可能得到的顺序是( )。 

    A. bacde                B. dbace              C. dbcae                D. ecbad

    解析:出队只能一端,所以abcde一定是这个顺序。

    反模拟入队,每次只能在两边出元素。

     

    栈/队列 互相模拟实现

     

    用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。

    思路:大概这么想:用一个辅助栈把进第一个栈的元素倒一下就好了。

    比如进栈1,2,3,4,5

    第一个栈:

    5

    4

    3

    2

    1

    然后倒到第二个栈里

    1

    2

    3

    4

    5

    再倒出来,顺序为1,2,3,4,5

    实现队列

    然后要注意的事情:

    1)栈2非空不能往里面倒数,顺序就错了。栈2没数再从栈1倒。

    2)栈1要倒就一次倒完,不倒完的话,进新数也会循序不对。

    import java.util.Stack;
     
    public class Solution {
        Stack<Integer> stack1 = new Stack<Integer>();
        Stack<Integer> stack2 = new Stack<Integer>();
         
        public void push(int node) {
            stack1.push(node);
        }
         
        public int pop() {
            if(stack1.empty()&&stack2.empty()){
                throw new RuntimeException("Queue is empty!");
            }
            if(stack2.empty()){
                while(!stack1.empty()){
                    stack2.push(stack1.pop());
                }
            }
            return stack2.pop();
        }
    }

     

    用两个队列实现栈,要求同上:

    这其实意义不是很大,有些数据结构书上甚至说两个队列不能实现栈。

    其实是可以的,只是时间复杂度较高,一个弹出操作时间为O(N)。

    思路:两个队列,编号为1和2.

    进栈操作:进1号队列

    出栈操作:把1号队列全弄到2号队列里,剩最后一个别压入,而是返回。

    最后还得把1和2号换一下,因为现在是2号有数,1号空。

     

    仅仅有思考价值,不实用。

    比如压入1,2,3

    队列1:1,2,3

    队列2:空

    依次弹出1,2,3:

    队列1里的23进入2号,3弹出

    队列1:空

    队列2:2,3

     

    队列2中3压入1号,2弹出

    队列1:3

    队列2:空

     

    队列1中只有一个元素,弹出。

     

    上代码:

    public class TwoQueueImplStack {
    	Queue<Integer> queue1 = new ArrayDeque<Integer>();
    	Queue<Integer> queue2 = new ArrayDeque<Integer>();
    //压入
    	public void push(Integer element){
    		//都为空,优先1
    		if(queue1.isEmpty() && queue2.isEmpty()){
    			queue1.add(element);
    			return;
    		}
    		//1为空,2有数据,放入2
    		if(queue1.isEmpty()){
    			queue2.add(element);
    			return;
    		}
    		//2为空,1有数据,放入1
    		if(queue2.isEmpty()){
    			queue1.add(element);
    			return;
    		}
    	}
    //弹出
    	public Integer pop(){
    		//两个都空,异常
    		if(queue1.isEmpty() && queue2.isEmpty()){
    			try{
    				throw new Exception("satck is empty!");
    			}catch(Exception e){
    				e.printStackTrace();
    			}
    		}	
    		//1空,2有数据,将2中的数据依次放入1,最后一个元素弹出
    		if(queue1.isEmpty()){
    			while(queue2.size() > 1){
    				queue1.add(queue2.poll());
    			}
    			return queue2.poll();
    		}
    		
    		//2空,1有数据,将1中的数据依次放入2,最后一个元素弹出
    		if(queue2.isEmpty()){
    			while(queue1.size() > 1){
    				queue2.add(queue1.poll());
    			}
    			return queue1.poll();
    		}
    		
    		return (Integer)null;
    	}
    //测试
    	public static void main(String[] args) {
    		TwoQueueImplStack qs = new TwoQueueImplStack();
    		qs.push(2);
    		qs.push(4);
    		qs.push(7);
    		qs.push(5);
    		System.out.println(qs.pop());
    		System.out.println(qs.pop());
    		
    		qs.push(1);
    		System.out.println(qs.pop());
    	}
    }
    

     

    第八次笔记 (串)

    串的概念:串(字符串):是由 0 个或多个字符组成的有限序列。 通常记为:s =‘ a1 a2 a3 … ai …an  ’ ( n≥0 )。

    串的逻辑结构和线性表极为相似。

     

    一些串的类型:

     

    空串:不含任何字符的串,长度 = 0。

    空格串:仅由一个或多个空格组成的串。

    子串:由串中任意个连续的字符组成的子序列。

    主串:包含子串的串。

    位置:字符在序列中的序号。

    子串在主串中的位置:子串的首字符在主串中的位置。

     

    空串是任意串的子串,任意串是其自身的子串。

    串相等的条件:当两个串的长度相等且各个对应位置的字符都相等时才相等。

     

    实现:

     

    因为串是特殊的线性表,故其存储结构与线性表的 存储结构类似,只不过组成串的结点是单个字符。

     

    定长顺序存储表示

    也称为静态存储分配的顺序串。 即用一组地址连续的存储单元依次存放串中的字符序列。

     

    串长:可能首字符记录(显式)或\0结尾(隐式)

     

    定长顺序存储表示时串操作的缺点 :串的某些操作受限(截尾),如串的联接、插入、置换

     

    堆分配存储表示  

     

    存储空间在程序执行过程中动态分配,malloc() 分配一块实际串长所需要的存储空间(“堆”)

    堆存储结构的优点:堆存储结构既有顺序存储 结构的特点,处理(随机取子串)方便,操作中对 串长又没有任何限制,更显灵活,因此在串处理的 应用程序中常被采用。

     

    串的块链存储表示

    为了提高空间利用率,可使每个结点存放多个字符 (这是顺序串和链串的综合 (折衷) ),称为块链结构。

     优点:便于插入和删除    缺点:空间利用率低 

     

    串的定长表示

    思想和代码都不难,和线性表也差不多,串本来就是数据受限的线性表。

    串连接:

     

    #include <stdio.h>
    #include <string.h>
    //串的定长顺序存储表示
    #define MAXSTRLEN 255							//用户可在255以内定义最大串长
    typedef unsigned char SString[MAXSTRLEN + 1];	//0号单元存放串的长度
    
    int Concat(SString *T,SString S1,SString S2)
    	//用T返回S1和S2联接而成的新串。若未截断返回1,若截断返回0
    {
    	int i = 1,j,uncut = 0;
    	if(S1[0] + S2[0] <= MAXSTRLEN)	//未截断
    	{
    		for (i = 1; i <= S1[0]; i++)//赋值时等号不可丢
    			(*T)[i] = S1[i];
    		for (j = 1; j <= S2[0]; j++)
    			(*T)[S1[0]+j] = S2[j];	//(*T)[i+j] = S2[j]
    		(*T)[0] = S1[0] + S2[0];
    		uncut = 1;
    	}
    	else if(S1[0] < MAXSTRLEN)		//截断
    	{
    		for (i = 1; i <= S1[0]; i++)//赋值时等号不可丢
    			(*T)[i] = S1[i];
    
    		for (j = S1[0] + 1; j <= MAXSTRLEN; j++)
    		{
    			(*T)[j] = S2[j - S1[0] ];
    			(*T)[0] = MAXSTRLEN;
    			uncut = 0;
    		}
    	}
    	else
    	{
    		for (i = 0; i <= MAXSTRLEN; i++)
    			(*T)[i] = S1[i];
    		/*或者分开赋值,先赋值内容,再赋值长度
    		for (i = 1; i <= MAXSTRLEN; i++)
    			(*T)[i] = S1[i];
    		(*T)[0] = MAXSTRLEN;
    		*/
    		uncut = 0;
    	}
    
    	return uncut;
    }
    
    int SubString(SString *Sub,SString S,int pos,int len)
    	//用Sub返回串S的第pos个字符起长度为len的子串
    	//其中,1 ≤ pos ≤ StrLength(S)且0 ≤ len ≤ StrLength(S) - pos + 1(从pos开始到最后有多少字符)
    	//第1个字符的下标为1,因为第0个字符存放字符长度
    {
    	int i;
    	if(pos < 1 || pos > S[0] || len < 0 || len > S[0] - pos + 1)
    		return 0;
    	for (i = 1; i <= len; i++)
    	{
    		//S中的[pos,len]的元素 -> *Sub中的[1,len]
    		(*Sub)[i] = S[pos + i - 1];//下标运算符 > 寻址运算符的优先级
    	}
    	(*Sub)[0] = len;
    	return 1;
    }
    void PrintStr(SString S)
    {
    	int i;
    	for (i = 1; i <= S[0]; i++)
    		printf("%c",S[i]);
    	printf("\n");
    }
    
    
    int main(void)
    {
    	/*定长顺序存储初始化和打印的方法
    	SString s = {4,'a','b','c','d'};
    	int i;
    	//s = "abc";	//不可直接赋值
    	for (i = 1; i <= s[0]; i++)
    		printf("%c",s[i]);
    	*/
    	SString s1 = {4,'a','b','c','d'};
    	SString s2 = {4,'e','f','g','h'},s3;
    	SString T,Sub;
    	int i;
    	
    	for (i = 1; i <= 255; i++)
    	{
    		s3[i] = 'a';
    		if(i >= 248)
    			s3[i] = 'K';
    	}
    	s3[0] = 255;
    	SubString(&Sub,s3,247,8);
    	PrintStr(Sub);
    	
    
    
    
    	return 0;
    }

    第九次笔记(数组,广义表)

    数组:按一定格式排列起来的具有相同类型的数据元素的集合。

     

    二维数组:若一维数组中的数据元素又是一维数组结构,则称为二维数组。 

    同理,推广到多维数组。若 n -1 维数组中的元素又是一个一维数组结构,则称作 n 维数组。 

    声明格式:数据类型   变量名称[行数] [列数] ;

     

    实现:一般都是采用顺序存储结构来表示数组。

     

    二维数组两种顺序存储方式:以行序为主序 (低下标优先) 、以列序为主序 (高下标优先)

    一个二维数组 A,行下标的范围是 1 到 6,列下标的范围是 0 到 7,每个数组元素用相邻的6 个字节存储,存储器按字节编址。那么,这个数组的体积是288个字节。

     

     广义表(又称列表 Lists)是n≥0个元素 a1, a2, …, an 的有限序列,其中每一个ai 或者是原子,或者是一个子表。

     

    表头:若 LS 非空 (n≥1 ),则其第一个元素 a1 就是表头。

     表尾:除表头之外的其它元素组成的表。记作  tail(LS) = (a2, ..., an)。 

     

    (( )) 长度为 1,表头、表尾均为 ( )

    (a, (b, c))长度为 2,由原子 a 和子表 (b, c) 构成。表头为 a ;表尾为 ((b, c))。

     

    广义表的长度定义为最外层所包含元素的个数

    广义表的深度定义为该广义表展开后所含括号的重数。

    “原子”的深度为 0 ;  “空表”的深度为 1 。

     

    取表头运算 GetHead  和取表尾运算 GetTail

    GetHead(LS) = a1        GetTail(LS) = (a2, …, an)。

     

    广义表可看成是线性表的推广,线性表是广义表的特例。

     

    广义表的结构相当灵活,在某种前提下,它可以兼容线 性表、数组、树和有向图等各种常用的数据结构。

    由于广义表不仅集中了线性表、数组、树和有向图等常 见数据结构的特点,而且可有效地利用存储空间,因此在计算机的许多应用领域都有成功使用广义表的实例。 

     

     

    第十次笔记(树和二叉树)

     

    树的定义:树(Tree)是 n(n≥0)个结点的有限集。若 n=0,称为空树;若 n > 0,则它满足如下两个条件:  

    (1)  有且仅有一个特定的称为根 (Root) 的结点;  

    (2)  其余结点可分为 m (m≥0) 个互不相交的有限集 T1, T2, T3, …, Tm, 其中每一个集合本身又是一棵树,并称为根的子树 (SubTree)。

    显然,树的定义是一个递归的定义。

    树的一些术语:

    • 结点的度(Degree):结点的子树个数;
    • 树的度:树的所有结点中最大的度数;
    • 叶结点(Leaf):度为0的结点;
    • 父结点(Parent):有子树的结点是其子树的根节点的父结点;
    • 子结点/孩子结点(Child):若A结点是B结点的父结点,则称B结点是A结点的子结点;
    • 兄弟结点(Sibling):具有同一个父结点的各结点彼此是兄弟结点;
    • 路径和路径长度:从结点n1到nk的路径为一个结点序列n1,n2,...,nk。ni是ni+1的父结点。路径所包含边的个数为路径的长度;
    • 祖先结点(Ancestor):沿树根到某一结点路径上的所有结点都是这个结点的祖先结点;
    • 子孙结点(Descendant):某一结点的子树中的所有结点是这个结点的子孙;
    • 结点的层次(Level):规定根结点在1层,其他任一结点的层数是其父结点的层数加1;
    • 树的深度(Depth):树中所有结点中的最大层次是这棵树的深度;

    将树中节点的各子树看成从左至右是有次序的(即不能互换),则称为该树是有序树,否则称为无序树

    森林(forest)是 m (m≥0) 棵互不相交的树的集合。

     

    二叉树

     

    在计算机科学中,二叉树是每个结点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。二叉树常被用于实现二叉查找树和二叉堆。

     

    虽然二叉树与树概念不同,但有关树的基本术语对二叉树都适用。

     

    二叉树结点的子树要区分左子树和右子树,即使只有一 棵子树也要进行区分,说明它是左子树,还是右子树。树当 结点只有一个孩子时,就无须区分它是左还是右。

     

    注意:尽管二叉树与树有许多相似之处,但二叉树不是树的特殊情形。

    一些性质:

    在二叉树的第 i 层上至多有  个结点 (i ≥1)。

    证明:每个节点至多两个孩子,每一层至多比上一层多一倍的结点,根为1.

     

    深度为 k 的二叉树至多有 个结点(k ≥1)。

    证明:把每一层最大节点加起来即可

     

    对任何一棵二叉树 T,如果其叶子数为 n0,度为 2的结点数为 n2,则 n0 = n2 + 1。

    证明:对于一个只有根的树,n0 = n2 + 1成立。1=0+1

    我们把一个叶子节点换成度为2的结点:

    黑色节点原来为叶子节点

    我们发现,度为2的结点数加1(黑色节点);叶子节点数加1(原来的去掉,新增两个);对于式子n0 = n2 + 1没影响,还是成立。

     

    我们把叶子节点换成度为1的结点,比如没有右孩子。

    我们发现,度为2的结点数没变。叶子节点数没变(减了一个加了一个)

    所以,不管你怎么换,公式都成立。(佛系证明)

     

     

    二叉树概述

     

    各种实现和应用以后放链接

    一、二叉树的基本概念

    二叉树:二叉树是每个节点最多有两个子树的树结构。

    根节点:一棵树最上面的节点称为根节点。

    父节点子节点:如果一个节点下面连接多个节点,那么该节点称为父节点,它下面的节点称为子 节点。

    叶子节点:没有任何子节点的节点称为叶子节点。

    兄弟节点:具有相同父节点的节点互称为兄弟节点。

    节点度:节点拥有的子树数。上图中,13的度为2,46的度为1,28的度为0。

    树的深度:从根节点开始(其深度为0)自顶向下逐层累加的。上图中,13的深度是1,30的深度是2,28的深度是3。

    树的高度:从叶子节点开始(其高度为0)自底向上逐层累加的。54的高度是2,根节点23的高度是3。

    对于树中相同深度的每个节点来说,它们的高度不一定相同,这取决于每个节点下面的叶子节点的深度。上图中,13和54的深度都是1,但是13的高度是1,54的高度是2。

    二、二叉树的类型

    类型定义图示

    满二叉树

    Full Binary Tree

    除最后一层无任何子节点外,每一层上的所有节点都有两个子节点,最后一层都是叶子节点。满足下列性质:

    1)一颗树深度为h,最大层数为k,深度与最大层数相同,k=h;

    2)叶子节点数(最后一层)为2k−1;

    3)第 i 层的节点数是:2i−1;

    4)总节点数是:2k−1,且总节点数一定是奇数。

    完全二叉树

    Complete Binary Tree

    若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。满足下列性质:

    1)只允许最后一层有空缺结点且空缺在右边,即叶子节点只能在层次最大的两层上出现;

    2)对任一节点,如果其右子树的深度为j,则其左子树的深度必为j或j+1。 即度为1的点只有1个或0个;

    3)除最后一层,第 i 层的节点数是:2i−1;

    4)有n个节点的完全二叉树,其深度为:log2n+1或为log2n+1;

    5)满二叉树一定是完全二叉树,完全二叉树不一定是满二叉树。

    平衡二叉树

    Balanced Binary Tree

    又被称为AVL树,它是一颗空树或左右两个子树的高度差的绝对值不超过 1,并且左右两个子树都是一棵平衡二叉树。

    二叉搜索树

    Binary Search Tree

    又称二叉查找树、二叉排序树(Binary Sort Tree)。它是一颗空树或是满足下列性质的二叉树:

    1)若左子树不空,则左子树上所有节点的值均小于或等于它的根节点的值;

    2)若右子树不空,则右子树上所有节点的值均大于或等于它的根节点的值;

    3)左、右子树也分别为二叉排序树。

    红黑树

    Red Black Tree

    是每个节点都带有颜色属性(颜色为红色或黑色)的自平衡二叉查找树,满足下列性质:

    1)节点是红色或黑色;

    2)根节点是黑色;

    3)所有叶子节点都是黑色;

    4)每个红色节点必须有两个黑色的子节点。(从每个叶子到根的所有路径上不能有两个连续的红色节点。)

    5)从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。

     

     

     

     

     

     

     

     

     

    啦啦啦

     

     

    第十一次笔记(满二叉树,完全二叉树)

    因为图片丢失,内容不全,我尽量找一下图

    满二叉树 (Full binary tree)

    除最后一层无任何子节点外,每一层上的所有结点都有两个子结点二叉树。

    国内教程定义:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。

    国外(国际)定义:a binary tree T is full if each node is either a leaf or possesses exactly two childnodes.

    大意为:如果一棵二叉树的结点要么是叶子结点,要么它有两个子结点,这样的树就是满二叉树。(一棵满二叉树的每一个结点要么是叶子结点,要么它有两个子结点,但是反过来不成立,因为完全二叉树也满足这个要求,但不是满二叉树)

    从图形形态上看,满二叉树外观上是一个三角形。

    这里缺失公式

    完全二叉树

     

    完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。

    可以根据公式进行推导,假设n0是度为0的结点总数(即叶子结点数),n1是度为1的结点总数,n2是度为2的结点总数,则 :

    ①n= n0+n1+n2 (其中n为完全二叉树的结点总数);又因为一个度为2的结点会有2个子结点,一个度为1的结点会有1个子结点,除根结点外其他结点都有父结点,

    ②n= 1+n1+2*n2 ;由①、②两式把n2消去得:n= 2*n0+n1-1,由于完全二叉树中度为1的结点数只有两种可能0或1,由此得到n0=n/2 或 n0=(n+1)/2。

    简便来算,就是 n0=n/2,其中n为奇数时(n1=0)向上取整;n为偶数时(n1=1)。可根据完全二叉树的结点总数计算出叶子结点数。

     

    重点:出于简便起见,完全二叉树通常采用数组而不是链表存储

     

    对于tree[i]有如下特点:

    (1)若i为奇数且i>1,那么tree的左兄弟为tree[i-1];

    (2)若i为偶数且i<n,那么tree的右兄弟为tree[i+1];

    (3)若i>1,tree的父亲节点为tree[i div 2];

    (4)若2*i<=n,那么tree的左孩子为tree[2*i];若2*i+1<=n,那么tree的右孩子为tree[2*i+1];

    (5)若i>n div 2,那么tree[i]为叶子结点(对应于(3));

    (6)若i<(n-1) div 2.那么tree[i]必有两个孩子(对应于(4))。

    (7)满二叉树一定是完全二叉树,完全二叉树不一定是满二叉树。

    完全二叉树第i层至多有2^(i-1)个节点,共i层的完全二叉树最多有2^i-1个节点。

    特点:

    1)只允许最后一层有空缺结点且空缺在右边,即叶子结点只能在层次最大的两层上出现;

    2)对任一结点,如果其右子树的深度为j,则其左子树的深度必为j或j+1。 即度为1的点只有1个或0个

     

    第十二次笔记(二叉树的存储和遍历)

     

    顺序存储结构

     

    完全二叉树:用一组地址连续的 存储单元依次自上而下、自左至右存 储结点元素,即将编号为 i  的结点元 素存储在一维数组中下标为 i –1 的分量中。

    一般二叉树:将其每个结点与完 全二叉树上的结点相对照,存储在一 维数组的相应分量中。

     

    最坏情况:树退化为线性后:

    我们要把它“变”成这个大家伙来存了:

    深度为 k 的且只 有 k 个结点的右单支树需要 长度为2^k-1 的一维数组。 

     

    链式存储结构

    lchild和rchild都是指向相同结构的指针

    在 n 个结点的二叉链表中有 n + 1 个空指针域。

    typedef struct BiTNode { // 结点结构
        TElemType data;
        struct BiTNode *lchild,*rchild;// 左右孩子指针
    } BiTNode, *BiTree;
    

    可以多一条指向父的指针。

     

    遍历二叉树

     

    顺着某一条搜索路径巡访二叉树中的结点,使   得每个结点均被访问一次,而且仅被访问一次

     “访问”的含义很广,可以是对结点作各种处理, 如:输出结点的信息、修改结点的数据值等,但要求这种访问不破坏原来的数据结构。

    (所以有些题目比如morris遍历、链表后半段反转判断回文等等必须进行完,解题时就算已经得出答案也要遍历完,因为我们不能改变原来的数据结构。)

     

    具体遍历的介绍

    https://blog.csdn.net/hebtu666/article/details/82853988

     

    深入理解二叉树遍历

    二叉树:二叉树是每个节点最多有两个子树的树结构。

     

    本文介绍二叉树的遍历相关知识。

    我们学过的基本遍历方法,无非那么几个:前序,中序,后序,还有按层遍历等等。

    设L、D、R分别表示遍历左子树、访问根结点和遍历右子树, 则对一棵二叉树的遍历有三种情况:DLR(称为先根次序遍历),LDR(称为中根次序遍历),LRD (称为后根次序遍历)。

    首先我们定义一颗二叉树

    typedef char ElementType;
    typedef struct TNode *Position;
    typedef Position BinTree;
    struct TNode{
        ElementType Data;
        BinTree Left;
        BinTree Right;
    };
    

    前序

    首先访问根,再先序遍历左(右)子树,最后先序遍历右(左)子树

    思路:

    就是利用函数,先打印本个节点,然后对左右子树重复此过程即可。

    void PreorderTraversal( BinTree BT )
    {
        if(BT==NULL)return ;
        printf(" %c", BT->Data);
        PreorderTraversal(BT->Left);
        PreorderTraversal(BT->Right);
    }

     

    中序

    首先中序遍历左(右)子树,再访问根,最后中序遍历右(左)子树

    思路:

    还是利用函数,先对左边重复此过程,然后打印根,然后对右子树重复。

    void InorderTraversal( BinTree BT )
    {
        if(BT==NULL)return ;
        InorderTraversal(BT->Left);
        printf(" %c", BT->Data);
        InorderTraversal(BT->Right);
    }

    后序

    首先后序遍历左(右)子树,再后序遍历右(左)子树,最后访问根

    思路:

    先分别对左右子树重复此过程,然后打印根

    void PostorderTraversal(BinTree BT)
    {
        if(BT==NULL)return ;
        PostorderTraversal(BT->Left);
        PostorderTraversal(BT->Right);
        printf(" %c", BT->Data);
    }

    进一步思考

    看似好像很容易地写出了三种遍历。。。。。

     

    但是你真的理解为什么这么写吗?

    比如前序遍历,我们真的是按照定义里所讲的,首先访问根,再先序遍历左(右)子树,最后先序遍历右(左)子树。这种过程来遍历了一遍二叉树吗?

    仔细想想,其实有一丝不对劲的。。。

    再看代码:

    void Traversal(BinTree BT)//遍历
    {
    //1111111111111
        Traversal(BT->Left);
    //22222222222222
        Traversal(BT->Right);
    //33333333333333
    }

    为了叙述清楚,我给三个位置编了号 1,2,3

    我们凭什么能前序遍历,或者中序遍历,后序遍历?

    我们看,前序中序后序遍历,实现的代码其实是类似的,都是上面这种格式,只是我们分别在位置1,2,3打印出了当前节点而已啊。我们凭什么认为,在1打印,就是前序,在2打印,就是中序,在3打印,就是后序呢?不管在位置1,2,3哪里操作,做什么操作,我们利用函数遍历树的顺序变过吗?当然没有啊。。。

    都是三次返回到当前节点的过程:先到本个节点,也就是位置1,然后调用了其他函数,最后调用完了,我们开到了位置2。然后又调用别的函数,调用完了,我们来到了位置3.。然后,最后操作完了,这个函数才结束。代码里的三个位置,每个节点都被访问了三次。

    而且不管位置1,2,3打印了没有,操作了没有,这个顺序是永远存在的,不会因为你在位置1打印了,顺序就改为前序,你在位置2打印了,顺序就成了中序。

     

    为了有更直观的印象,我们做个试验:在位置1,2,3全都放入打印操作;

    我们会发现,每个节点都被打印了三次。而把每个数第一次出现拿出来,就组成了前序遍历的序列;所有数字第二次出现拿出来,就组成了中序遍历的序列。。。。

     

    其实,遍历是利用了一种数据结构:栈

    而我们这种写法,只是通过函数,来让系统帮我们压了栈而已。为什么能实现遍历?为什么我们访问完了左子树,能返回到当前节点?这都是栈的功劳啊。我们把当前节点(对于函数就是当时的现场信息)存到了栈里,记录下来,后来才能把它拿了出来,能回到以前的节点。

     

    想到这里,可能就有更深刻的理解了。

    我们能否不用函数,不用系统帮我们压栈,而是自己做一个栈,来实现遍历呢?

    先序实现思路:拿到一个节点的指针,先判断是否为空,不为空就先访问(打印)该结点,然后直接进栈,接着遍历左子树;为空则要从栈中弹出一个节点来,这个时候弹出的结点就是其父亲,然后访问其父亲的右子树,直到当前节点为空且栈为空时,结束。

    核心思路代码实现:

    *p=root;
    while(p || !st.empty())
    {
        if(p)//非空
        {
            //visit(p);进行操作
            st.push(p);//入栈
            p = p->lchild;左
        } 
        else//空
        {
            p = st.top();//取出
            st.pop();
            p = p->rchild;//右
        }
    }

    中序实现思路:和前序遍历一样,只不过在访问节点的时候顺序不一样,访问节点的时机是从栈中弹出元素时访问,如果从栈中弹出元素,就意味着当前节点父亲的左子树已经遍历完成,这时候访问父亲,就是中序遍历.

    (对应递归是第二次遇到)

    核心代码实现:

    *p=root;
    while(p || !st.empty())
    {
        if(p)//非空
        {
            st.push(p);//压入
            p = p->lchild;
        }
        else//空
        {
            p = st.top();//取出
            //visit(p);操作
            st.pop();
            p = p->rchild;
        }
    }

    后序遍历是最难的。因为要保证左孩子和右孩子都已被访问并且左孩子在右孩子前访问才能访问根结点,这就为流程的控制带来了难点。

    因为我们原来说了,后序是第三次遇到才进行操作的,所以我们很容易有这种和递归函数类似的思路:对于任一结点,将其入栈,然后沿其左子树一直往下走,一直走到没有左孩子的结点,此时该结点在栈顶,但是不能出栈访问, 因此右孩子还没访问。所以接下来按照相同的规则对其右子树进行相同的处理。访问完右孩子,该结点又出现在栈顶,此时可以将其出栈并访问。这样就保证了正确的访问顺序。可以看出,在这个过程中,每个结点都两次出现在栈顶,只有在第二次出现在栈顶时,才能访问它。因此需要多设置一个变量标识该结点是否是第一次出现在栈顶。

    第二种思路:对于任一结点P,先将其入栈。如果P不存在左孩子和右孩子,或者左孩子和右孩子都已被访问过了,就可以直接访问该结点。如果有孩子未访问,将P的右孩子和左孩子依次入栈。

    网上的思路大多是第一种,所以我在这里给出第二种的大概实现吧

    首先初始化cur,pre两个指针,代表访问的当前节点和之前访问的节点。把根放入,开始执行。

    s.push(root);
    while(!s.empty())
    {
        cur=s.top();
        if((cur->lchild==NULL && cur->rchild==NULL)||(pre!=NULL && (pre==cur->lchild||pre==cur->rchild)))
        {
            //visit(cur);  如果当前结点没有孩子结点或者孩子节点都已被访问过 
            s.pop();//弹出
            pre=cur; //记录
        }
        else//分别放入右左孩子
        {
            if(cur->rchild!=NULL)
                s.push(cur->rchild);
            if(cur->lchild!=NULL)    
                s.push(cur->lchild);
        }
    }

    这两种方法,都是利用栈结构来实现的遍历,需要一定的栈空间,而其实存在一种时间O(N),空间O(1)的遍历方式,下次写了我再放链接。

     

    斗个小机灵:后序是LRD,我们其实已经知道先序是DLR,那其实我们可以用先序来实现后序啊,我们只要先序的时候把左右子树换一下:DRL(这一步很好做到),然后倒过来不就是DRL了嘛。。。。。就把先序代码改的左右反过来,然后放栈里倒过来就好了,不需要上面介绍的那些复杂的方法。。。。

    第十四次笔记(树的存储)

     

     

    父节点表示法

     

    数据域:存放结点本身信息。

    双亲域:指示本结点的双亲结点在数组中的位置。

    对应的树:

    /* 树节点的定义 */
    #define MAX_TREE_SIZE 100
     
    typedef struct{
        TElemType data;
        int parent; /* 父节点位置域 */
    } PTNode;
     
    typedef struct{
        PTNode nodes[MAX_TREE_SIZE];
        int n; /* 节点数 */
    } PTree;

    特点:找双亲容易,找孩子难。

    孩子表示法(树的链式存储结构)

     

    childi指向一个结点

    可以加上parent。

    在有 n 个结点、度为  d 的树的 d 叉链表中,有  n×(d-1)+1 个空链域

     

    我们可以用degree记录有几个孩子,省掉空间,但是结点的指针个数不相等,为该结点的度 degree。

     

    孩子链表:

     

    把每个结点的孩子结点排列起来,看成是一个线性表,用单链表存储,则 n 个结点有 n 个孩子链表(叶子的孩子链表为空表)。而 n 个头指针又组成一个线性表,用顺序表(含 n 个元素的结构数组)存储。

    孩子兄弟表示法(二叉树表示法)

    用二叉链表作树的存储结构,链表中每个结点的两个指针域分别指向其第一个孩子结点和下一个兄弟结点

    typedef struct CSNode{
         ElemType data;
         struct CSNode *firstchild, *nextsibling;  
    } CSNode, *CSTree;
    

    第十五次笔记(图基础)

     

    图是一种:   数据元素间存在多对多关系的数据结构   加上一组基本操作构成的抽象数据类型。

    图 (Graph) 是一种复杂的非线性数据结构,由顶点集合及顶点间的关系(也称弧或边)集合组成。可以表示为: G=(V, VR)  

    其中 V 是顶点的有穷非空集合;

    VR 是顶点之间   关系的有穷集合,也叫做弧或边集合。

    弧是顶点的有序对,边是顶点的无序对。

     

    特点:(相对于线性结构)

    顶点之间的关系是任意的 

    图中任意两个顶点之间都可能相关

    顶点的前驱和后继个数无限制

     

    相关概念:

     

    顶点(Vertex):图中的数据元素。线性表中我们把数据元素叫元素,树中将数据元素叫结点。

    边:顶点之间的逻辑关系用边来表示,边集可以是空的。

     

    无向边(Edge):若顶点V1到V2之间的边没有方向,则称这条边为无向边。

    无向图(Undirected graphs):图中任意两个顶点之间的边都是无向边。(A,D)=(D,A)

    无向图中边的取值范围:0≤e≤n(n-1)/2

    有向边:若从顶点V1到V2的边有方向,则称这条边为有向边,也称弧(Arc)。用<V1,V2>表示,V1为狐尾(Tail),V2为弧头(Head)。(V1,V2)≠(V2,V1)。

    有向图(Directed graphs):图中任意两个顶点之间的边都是有向边。

    有向图中弧的取值范围:0≤e≤n(n-1)

       注意:无向边用“()”,而有向边用“< >”表示。

     

    简单图:图中不存在顶点到其自身的边,且同一条边不重复出现。

    无向完全图:无向图中,任意两个顶点之间都存在边。

    有向完全图:有向图中,任意两个顶点之间都存在方向互为相反的两条弧。

    稀疏图:有很少条边。

    稠密图:有很多条边。

     

    邻接点:若 (v, v´) 是一条边,则称顶点 v 和 v´互为 邻接点,或称 v 和 v´相邻接;称边 (v, v´) 依附于顶点 v 和 v´,或称 (v, v´) 与顶点 v 和 v´ 相关联。

     

    权(Weight):与图的边或弧相关的数。

    网(Network):带权的图。

    子图(Subgraph):假设G=(V,{E})和G‘=(V',{E'}),如果V'包含于V且E'包含于E,则称G'为G的子图。

     

     入度:有向图中以顶点 v 为头的弧的数目称为 v 的入度,记为:ID(v)。  

    出度:有向图中以顶点 v 为尾的弧的数目称为 v 的出度,记为:OD(v)。

    度(Degree):无向图中,与顶点V相关联的边的数目。有向图中,入度表示指向自己的边的数目,出度表示指向其他边的数目,该顶点的度等于入度与出度的和。

     

    回路(环):第一个顶点和最后一个顶点相同的路径。

    简单路径:序列中顶点(两端点除外)不重复出现的路径。 

    简单回路(简单环):前后两端点相同的简单路径。

    路径的长度:一条路径上边或弧的数量。

     

    连通:从顶点 v 到 v´ 有路径,则说 v  和 v´ 是连通的。

    连通图:图中任意两个顶点都是连通的。

    连通分量:无向图的极大连通子图(不存在包含它的 更大的连通子图);

    任何连通图的连通分量只有一个,即其本身;非连通图有多个连通分量(非连通图的每一个连通部分)。

    强连通图: 任意两个顶点都连通的有向图。 

    强连通分量:有向图的极大强连通子图;任何强连通 图的强连通分量只有一个,即其本身;非强连通图有多个 强连通分量。

     

    生成树:所有顶点均由边连接在一起但不存在回路的图。(n个顶点n-1条边)

     

     

     

     

    图的存储

     

    多重链表:完全模拟图的样子,每个节点内的指针都指向该指向的节点。

    节点结构内指针数为度

    缺点:浪费空间、不容易操作

     

    数组表示法(邻接矩阵表示法)

    可用两个数组存储。其中一个 一维数组存储数据元素(顶点)的信息,另一个二维数组 (邻接矩阵)存储数据元素之间的关系(边或弧)的信息

    有向图:

    有向网:

    缺点:用于稀疏图时空间浪费严重

    优点:操作较容易

     

    邻接表

    指针数组存放每个结点,每个结点后接所有能到达的节点。

     

    图的遍历

     

    从图的任意指定顶点出发,依照某种规则去访问图中所有顶 点,且每个顶点仅被访问一次,这一过程叫做图的遍历。

    图的遍历按照深度优先和广度优先规则去实施,通常有深度 优先遍历法(Depth_First Search——DFS )和  广度优先遍历法 ( Breadth_Frist Search——BFS)两种。

    简单棋盘搜索https://blog.csdn.net/hebtu666/article/details/81483407

    别的实现以后再贴

    如何判别V的邻接点是否被访问?

    为每个顶点设立一个“访问标志”。

     

    最小生成树

    问题提出:
        要在n个城市间建立通信联络网。顶点:表示城市,权:城市间通信线路的花费代价。希望此通信网花费代价最小。
    问题分析:
        答案只能从生成树中找,因为要做到任何两个城市之间有线路可达,通信网必须是连通的;但对长度最小的要求可以知道网中显然不能有圈,如果有圈,去掉一条边后,并不破坏连通性,但总代价显然减少了,这与总代价最小的假设是矛盾的。
    结论:
        希望找到一棵生成树,它的每条边上的权值之和(即建立该通信网所需花费的总代价)最小 —— 最小代价生成树。
        构造最小生成树的算法很多,其中多数算法都利用了一种称之为 MST 的性质。
        MST 性质:设 N = (V, E)  是一个连通网,U是顶点集 V的一个非空子集。若边 (u, v) 是一条具有最小权值的边,其中u∈U,v∈V-U,则必存在一棵包含边 (u, v) 的最小生成树。


    (1)普里姆 (Prim) 算法

    算法思想: 
        ①设 N=(V, E)是连通网,TE是N上最小生成树中边的集合。
        ②初始令 U={u_0}, (u_0∈V), TE={ }。
        ③在所有u∈U,u∈U-V的边(u,v)∈E中,找一条代价最小的边(u_0,v_0 )。
        ④将(u_0,v_0 )并入集合TE,同时v_0并入U。
        ⑤重复上述操作直至U = V为止,则 T=(V,TE)为N的最小生成树。

     
    代码实现:

    void MiniSpanTree_PRIM(MGraph G,VertexType u)
        //用普里姆算法从第u个顶点出发构造网G的最小生成树T,输出T的各条边。
        //记录从顶点集U到V-U的代价最小的边的辅助数组定义;
        //closedge[j].lowcost表示在集合U中顶点与第j个顶点对应最小权值
    {
        int k, j, i;
        k = LocateVex(G,u);
        for (j = 0; j < G.vexnum; ++j)    //辅助数组的初始化
            if(j != k)
            {
                closedge[j].adjvex = u;
                closedge[j].lowcost = G.arcs[k][j].adj;    
    //获取邻接矩阵第k行所有元素赋给closedge[j!= k].lowcost
            }
        closedge[k].lowcost = 0;        
    //初始,U = {u};  
        PrintClosedge(closedge,G.vexnum);
        for (i = 1; i < G.vexnum; ++i)    \
    //选择其余G.vexnum-1个顶点,因此i从1开始循环
        {
            k = minimum(G.vexnum,closedge);        
    //求出最小生成树的下一个结点:第k顶点
            PrintMiniTree_PRIM(G, closedge, k);     //输出生成树的边
            closedge[k].lowcost = 0;                //第k顶点并入U集
            PrintClosedge(closedge,G.vexnum);
            for(j = 0;j < G.vexnum; ++j)
            {                                           
                if(G.arcs[k][j].adj < closedge[j].lowcost)    
    //比较第k个顶点和第j个顶点权值是否小于closedge[j].lowcost
                {
                    closedge[j].adjvex = G.vexs[k];//替换closedge[j]
                    closedge[j].lowcost = G.arcs[k][j].adj;
                    PrintClosedge(closedge,G.vexnum);
                }
            }
        }
    }


    (2)克鲁斯卡尔 (Kruskal) 算法

    算法思想: 
        ①设连通网  N = (V, E ),令最小生成树初始状态为只有n个顶点而无边的非连通图,T=(V, { }),每个顶点自成一个连通分量。
        ②在 E 中选取代价最小的边,若该边依附的顶点落在T中不同的连通分量上(即:不能形成环),则将此边加入到T中;否则,舍去此边,选取下一条代价最小的边。
    ③依此类推,直至 T 中所有顶点都在同一连通分量上为止。
          
        最小生成树可能不惟一!

     

    拓扑排序

    (1)有向无环图

        无环的有向图,简称 DAG (Directed Acycline Graph) 图。
     
    有向无环图在工程计划和管理方面的应用:除最简单的情况之外,几乎所有的工程都可分为若干个称作“活动”的子工程,并且这些子工程之间通常受着一定条件的约束,例如:其中某些子工程必须在另一些子工程完成之后才能开始。
    对整个工程和系统,人们关心的是两方面的问题: 
    ①工程能否顺利进行; 
    ②完成整个工程所必须的最短时间。

    对应到有向图即为进行拓扑排序和求关键路径。 
    AOV网: 
        用一个有向图表示一个工程的各子工程及其相互制约的关系,其中以顶点表示活动,弧表示活动之间的优先制约关系,称这种有向图为顶点表示活动的网,简称AOV网(Activity On Vertex network)。
    例如:排课表
          
    AOV网的特点:
    ①若从i到j有一条有向路径,则i是j的前驱;j是i的后继。
    ②若< i , j >是网中有向边,则i是j的直接前驱;j是i的直接后继。
    ③AOV网中不允许有回路,因为如果有回路存在,则表明某项活动以自己为先决条件,显然这是荒谬的。


    问题:    
        问题:如何判别 AOV 网中是否存在回路?
        检测 AOV 网中是否存在环方法:对有向图构造其顶点的拓扑有序序列,若网中所有顶点都在它的拓扑有序序列中,则该AOV网必定不存在环。


    拓扑排序的方法:
        ①在有向图中选一个没有前驱的顶点且输出之。
        ②从图中删除该顶点和所有以它为尾的弧。
        ③重复上述两步,直至全部顶点均已输出;或者当图中不存在无前驱的顶点为止。
            
        一个AOV网的拓扑序列不是唯一的!
    代码实现:

    Status TopologicalSort(ALGraph G)
        //有向图G采用邻接表存储结构。
        //若G无回路,则输出G的顶点的一个拓扑序列并返回OK,否则返回ERROR.
        //输出次序按照栈的后进先出原则,删除顶点,输出遍历
    {
        SqStack S;
        int i, count;
        int *indegree1 = (int *)malloc(sizeof(int) * G.vexnum);
        int indegree[12] = {0};
        FindInDegree(G, indegree);    //求个顶点的入度下标从0开始
        InitStack(&S);
        PrintStack(S);
        for(i = 0; i < G.vexnum; ++i)
            if(!indegree[i])        //建0入度顶点栈S
                push(&S,i);        //入度为0者进栈
        count = 0;                //对输出顶点计数
        while (S.base != S.top)
        {
            ArcNode* p;
            pop(&S,&i);
            VisitFunc(G,i);//第i个输出栈顶元素对应的顶点,也就是最后进来的顶点    
            ++count;          //输出i号顶点并计数
            for(p = G.vertices[i].firstarc; p; p = p->nextarc)
            {    //通过循环遍历第i个顶点的表结点,将表结点中入度都减1
                int k = p->adjvex;    //对i号顶点的每个邻接点的入度减1
                if(!(--indegree[k]))
                    push(&S,k);        //若入度减为0,则入栈
            }//for
        }//while
        if(count < G.vexnum)
        {
            printf("\n该有向图有回路!\n");
            return ERROR;    //该有向图有回路
        }
        else
        {
            printf("\n该有向图没有回路!\n");
            return OK;
        }
    }


    关键路径

        把工程计划表示为有向图,用顶点表示事件,弧表示活动,弧的权表示活动持续时间。每个事件表示在它之前的活动已经完成,在它之后的活动可以开始。称这种有向图为边表示活动的网,简称为 AOE网 (Activity On Edge)。
    例如:
    设一个工程有11项活动,9个事件。
    事件v_1——表示整个工程开始(源点) 
    事件v_9——表示整个工程结束(汇点)

     
    对AOE网,我们关心两个问题:  
    ①完成整项工程至少需要多少时间? 
    ②哪些活动是影响工程进度的关键?
    关键路径——路径长度最长的路径。
    路径长度——路径上各活动持续时间之和。
    v_i——表示事件v_i的最早发生时间。假设开始点是v_1,从v_1到〖v�i〗的最长路径长度。ⅇ(ⅈ)——表示活动a_i的最早发生时间。
    l(ⅈ)——表示活动a_i最迟发生时间。在不推迟整个工程完成的前提下,活动a_i最迟必须开始进行的时间。
    l(ⅈ)-ⅇ(ⅈ)意味着完成活动a_i的时间余量。
    我们把l(ⅈ)=ⅇ(ⅈ)的活动叫做关键活动。显然,关键路径上的所有活动都是关键活动,因此提前完成非关键活动并不能加快工程进度。
        例如上图中网,从从v_1到v_9的最长路径是(v_1,v_2,v_5,v_8,ν_9 ),路径长度是18,即ν_9的最迟发生时间是18。而活动a_6的最早开始时间是5,最迟开始时间是8,这意味着:如果a_6推迟3天或者延迟3天完成,都不会影响整个工程的完成。因此,分析关键路径的目的是辨别哪些是关键活动,以便争取提高关键活动的工效,缩短整个工期。
        由上面介绍可知:辨别关键活动是要找l(ⅈ)=ⅇ(ⅈ)的活动。为了求ⅇ(ⅈ)和l(ⅈ),首先应求得事件的最早发生时间vⅇ(j)和最迟发生时间vl(j)。如果活动a_i由弧〈j,k〉表示,其持续时间记为dut(〈j,k〉),则有如下关系:
    ⅇ(ⅈ)= vⅇ(j)
    l(ⅈ)=vl(k)-dut(〈j,k〉)
        求vⅇ(j)和vl(j)需分两步进行:
    第一步:从vⅇ(0)=0开始向前递推
    vⅇ(j)=Max{vⅇ(i)+dut(〈j,k〉)}   〈i,j〉∈T,j=1,2,…,n-1
    其中,T是所有以第j个顶点为头的弧的集合。
    第二步:从vl(n-1)=vⅇ(n-1)起向后递推
    vl(i)=Min{vl(j)-dut(〈i,j〉)}  〈i,j〉∈S,i=n-2,…,0
    其中,S是所有以第i个顶点为尾的弧的集合。
    下面我们以上图AOE网为例,先求每个事件v_i的最早发生时间,再逆向求每个事件对应的最晚发生时间。再求每个活动的最早发生时间和最晚发生时间,如下面表格:
              
    在活动的统计表中,活动的最早发生时间和最晚发生时间相等的,就是关键活动


    关键路径的讨论:

    ①若网中有几条关键路径,则需加快同时在几条关键路径上的关键活动。      如:a11、a10、a8、a7。 
    ②如果一个活动处于所有的关键路径上,则提高这个活动的速度,就能缩短整个工程的完成时间。如:a1、a4。
    ③处于所有关键路径上的活动完成时间不能缩短太多,否则会使原关键路径变成非关键路径。这时必须重新寻找关键路径。如:a1由6天变成3天,就会改变关键路径。

    关键路径算法实现:

    int CriticalPath(ALGraph G)
    {    //因为G是有向网,输出G的各项关键活动
        SqStack T;
        int i, j;    ArcNode* p;
        int k , dut;
        if(!TopologicalOrder(G,T))
            return 0;
        int vl[VexNum];
        for (i = 0; i < VexNum; i++)
            vl[i] = ve[VexNum - 1];        //初始化顶点事件的最迟发生时间
        while (T.base != T.top)            //按拓扑逆序求各顶点的vl值
        {
     
            for(pop(&T, &j), p = G.vertices[j].firstarc; p; p = p->nextarc)
            {
                k = p->adjvex;    dut = *(p->info);    //dut<j, k>
                if(vl[k] - dut < vl[j])
                    vl[j] = vl[k] - dut;
            }//for
        }//while
        for(j = 0; j < G.vexnum; ++j)    //求ee,el和关键活动
        {
            for (p = G.vertices[j].firstarc; p; p = p->nextarc)
            {
                int ee, el;        char tag;
                k = p->adjvex;    dut = *(p->info);
                ee = ve[j];    el = vl[k] - dut;
                tag = (ee == el) ? '*' : ' ';
                PrintCriticalActivity(G,j,k,dut,ee,el,tag);
            }
        }
        return 1;
    }

     

    最短路 

    最短路

        典型用途:交通网络的问题——从甲地到乙地之间是否有公路连通?在有多条通路的情况下,哪一条路最短?
     
        交通网络用有向网来表示:顶点——表示城市,弧——表示两个城市有路连通,弧上的权值——表示两城市之间的距离、交通费或途中所花费的时间等。
        如何能够使一个城市到另一个城市的运输时间最短或运费最省?这就是一个求两座城市间的最短路径问题。
        问题抽象:在有向网中A点(源点)到达B点(终点)的多条路径中,寻找一条各边权值之和最小的路径,即最短路径。最短路径与最小生成树不同,路径上不一定包含n个顶点,也不一定包含n - 1条边。
       常见最短路径问题:单源点最短路径、所有顶点间的最短路径
    (1)如何求得单源点最短路径?
        穷举法:将源点到终点的所有路径都列出来,然后在其中选最短的一条。但是,当路径特别多时,特别麻烦;没有规律可循。
        迪杰斯特拉(Dijkstra)算法:按路径长度递增次序产生各顶点的最短路径。
    路径长度最短的最短路径的特点:
        在此路径上,必定只含一条弧 <v_0, v_1>,且其权值最小。由此,只要在所有从源点出发的弧中查找权值最小者。
    下一条路径长度次短的最短路径的特点:
    ①、直接从源点到v_2<v_0, v_2>(只含一条弧);
    ②、从源点经过顶点v_1,再到达v_2<v_0, v_1>,<v_1, v_2>(由两条弧组成)
    再下一条路径长度次短的最短路径的特点:
        有以下四种情况:
        ①、直接从源点到v_3<v_0, v_3>(由一条弧组成);
        ②、从源点经过顶点v_1,再到达v_3<v_0, v_1>,<v_1, v_3>(由两条弧组成);
        ③、从源点经过顶点v_2,再到达v_3<v_0, v_2>,<v_2, v_3>(由两条弧组成);
        ④、从源点经过顶点v_1  ,v_2,再到达v_3<v_0, v_1>,<v_1, v_2>,<v_2, v_3>(由三条弧组成);
    其余最短路径的特点:    
        ①、直接从源点到v_i<v_0, v_i>(只含一条弧);
        ②、从源点经过已求得的最短路径上的顶点,再到达v_i(含有多条弧)。
    Dijkstra算法步骤:
        初始时令S={v_0},  T={其余顶点}。T中顶点对应的距离值用辅助数组D存放。
        D[i]初值:若<v_0, v_i>存在,则为其权值;否则为∞。 
        从T中选取一个其距离值最小的顶点v_j,加入S。对T中顶点的距离值进行修改:若加进v_j作中间顶点,从v_0到v_i的距离值比不加 vj 的路径要短,则修改此距离值。
        重复上述步骤,直到 S = V 为止。

    算法实现:

    void ShortestPath_DIJ(MGraph G,int v0,PathMatrix &P,ShortPathTable &D)
    { // 用Dijkstra算法求有向网 G 的 v0 顶点到其余顶点v的最短路径P[v]及带权长度D[v]。
        // 若P[v][w]为TRUE,则 w 是从 v0 到 v 当前求得最短路径上的顶点。  P是存放最短路径的矩阵,经过顶点变成TRUE
        // final[v]为TRUE当且仅当 v∈S,即已经求得从v0到v的最短路径。
        int v,w,i,j,min;
        Status final[MAX_VERTEX_NUM];
        for(v = 0 ;v < G.vexnum ;++v)
        {
            final[v] = FALSE;
            D[v] = G.arcs[v0][v].adj;        //将顶点数组中下标对应是 v0 和 v的距离给了D[v]
            for(w = 0;w < G.vexnum; ++w)
                P[v][w] = FALSE;            //设空路径
            if(D[v] < INFINITY)
            {
                P[v][v0] = TRUE;
                P[v][v] = TRUE;
            }
        }
        D[v0]=0;
        final[v0]= TRUE; /* 初始化,v0顶点属于S集 */
        for(i = 1;i < G.vexnum; ++i) /* 其余G.vexnum-1个顶点 */
        { /* 开始主循环,每次求得v0到某个v顶点的最短路径,并加v到S集 */
            min = INFINITY; /* 当前所知离v0顶点的最近距离 */
            for(w = 0;w < G.vexnum; ++w)
                if(!final[w]) /* w顶点在V-S中 */
                    if(D[w] < min)
                    {
                        v = w;
                        min = D[w];
                    } /* w顶点离v0顶点更近 */
                    final[v] = TRUE; /* 离v0顶点最近的v加入S集 */
                    for(w = 0;w < G.vexnum; ++w) /* 更新当前最短路径及距离 */
                    {
                        if(!final[w] && min < INFINITY && G.arcs[v][w].adj < INFINITY && (min + G.arcs[v][w].adj < D[w]))
                        { /* 修改D[w]和P[w],w∈V-S */
                            D[w] = min + G.arcs[v][w].adj;
                            for(j = 0;j < G.vexnum;++j)
                                P[w][j] = P[v][j];
                            P[w][w] = TRUE;
                        }
                    }
        }
    }

     

    经典二分问题

    经典二分问题:给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target  。

    写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
    示例 1:

    输入: nums = [-1,0,3,5,9,12], target = 9。输出: 4
    解释: 9 出现在 nums 中并且下标为 4
    示例 2:

    输入: nums = [-1,0,3,5,9,12], target = 2。输出: -1
    解释: 2 不存在 nums 中因此返回 -1

    思路1:我们当然可以一个数一个数的遍历,但是毫无疑问要被大妈鄙视,这可怎么办呢?

    思路2:二分查找
    二分查找是一种基于比较目标值和数组中间元素的教科书式算法。

    如果目标值等于中间元素,则找到目标值。
    如果目标值较小,证明目标值小于中间元素及右边的元素,继续在左侧搜索。
    如果目标值较大,证明目标值大于中间元素及左边的元素,继续在右侧搜索。

    算法代码描述:

    初始化指针 left = 0, right = n - 1。
    当 left <= right:
    比较中间元素 nums[pivot] 和目标值 target 。
    如果 target = nums[pivot],返回 pivot。
    如果 target < nums[pivot],则在左侧继续搜索 right = pivot - 1。
    如果 target > nums[pivot],则在右侧继续搜索 left = pivot + 1。

    算法实现:照例贴出三种语言的实现,在Java实现中给出了详细注释

    class Solution {
      public int search(int[] nums, int target) {
    	//分别准备好左右端点
        int left = 0, right = nums.length - 1;
    	//循环二分
        while (left <= right) {
    	//取中点
          int pivot = left + (right - left) / 2;
    	  //找到答案并返回
          if (nums[pivot] == target) return pivot;
    	  //向左继续找
          if (target < nums[pivot]) right = pivot - 1;
    	  //向右继续找
          else left = pivot + 1;
        }
    	//未找到,返回-1
        return -1;
      }
    }
    class Solution:
        def search(self, nums: List[int], target: int) -> int:
            left, right = 0, len(nums) - 1
            while left <= right:
                pivot = left + (right - left) // 2
                if nums[pivot] == target:
                    return pivot
                if target < nums[pivot]:
                    right = pivot - 1
                else:
                    left = pivot + 1
            return -1
    class Solution {
      public:
      int search(vector<int>& nums, int target) {
        int pivot, left = 0, right = nums.size() - 1;
        while (left <= right) {
          pivot = left + (right - left) / 2;
          if (nums[pivot] == target) return pivot;
          if (target < nums[pivot]) right = pivot - 1;
          else left = pivot + 1;
        }
        return -1;
      }
    };

     

     

     

    二叉搜索树实现

    本文给出二叉搜索树介绍和实现

     

    首先说它的性质:所有的节点都满足,左子树上所有的节点都比自己小,右边的都比自己大。

     

    那这个结构有什么有用呢?

    首先可以快速二分查找。还可以中序遍历得到升序序列,等等。。。

    基本操作:

    1、插入某个数值

    2、查询是否包含某个数值

    3、删除某个数值

     

    根据实现不同,还可以实现其他很多种操作。

     

    实现思路思路:

    前两个操作很好想,就是不断比较,大了往左走,小了往右走。到空了插入,或者到空都没找到。

    而删除稍微复杂一些,有下面这几种情况:

    1、需要删除的节点没有左儿子,那就把右儿子提上去就好了。

    2、需要删除的节点有左儿子,这个左儿子没有右儿子,那么就把左儿子提上去

    3、以上都不满足,就把左儿子子孙中最大节点提上来。

     

    当然,反过来也是成立的,比如右儿子子孙中最小的节点。

     

    下面来叙述为什么可以这么做。

    下图中A为待删除节点。

    第一种情况:

     

    1、去掉A,把c提上来,c也是小于x的没问题。

    2、根据定义可知,x左边的所有点都小于它,把c提上来不影响规则。

     

    第二种情况

     

    3、B<A<C,所以B<C,根据刚才的叙述,B可以提上去,c可以放在b右边,不影响规则

    4、同理

     

    第三种情况

     

    5、注意:是把黑色的提升上来,不是所谓的最右边的那个,因为当初向左拐了,他一定小。

    因为黑色是最大,比B以及B所有的孩子都大,所以让B当左孩子没问题

    而黑点小于A,也就小于c,所以可以让c当右孩子

    大概证明就这样。。

    下面我们用代码实现并通过注释理解

    上次链表之类的用的c,循环来写的。这次就c++函数递归吧,不同方式练习。

    定义

    struct node
    {
        int val;//数据
        node *lch,*rch;//左右孩子
    };

    插入

     node *insert(node *p,int x)
     {
         if(p==NULL)//直到空就创建节点
         {
             node *q=new node;
             q->val=x;
             q->lch=q->rch=NULL;
             return p;
         }
         if(x<p->val)p->lch=insert(p->lch,x);
         else p->lch=insert(p->rch,x);
         return p;//依次返回自己,让上一个函数执行。
     }

    查找

     bool find(node *p,int x)
     {
         if(p==NULL)return false;
         else if(x==p->val)return true;
         else if(x<p->val)return find(p->lch,x);
         else return find(p->rch,x);
     }

    删除

     node *remove(node *p,int x)
     {
          if(p==NULL)return NULL;
          else if(x<p->val)p->lch=remove(p->lch,x);
          else if(x>p->val)p->lch=remove(p->rch,x);
          //以下为找到了之后
          else if(p->lch==NULL)//情况1
          {
              node *q=p->rch;
              delete p;
              return q;
          }
          else if(p->lch->rch)//情况2
          {
              node *q=p->lch;
              q->rch=p->rch;
              delete p;
              return q;
          }
          else
          {
              node *q;
              for(q=p->lch;q->rch->rch!=NULL;q=q->rch);//找到最大节点的前一个
              node *r=q->rch;//最大节点
              q->rch=r->lch;//最大节点左孩子提到最大节点位置
              r->lch=p->lch;//调整黑点左孩子为B
              r->rch=p->rch;//调整黑点右孩子为c
              delete p;//删除
              return r;//返回给父
          }
          return p;
     }

     

    对数组排序可以说是编程基础中的基础,本文对八种排序方法做简要介绍并用python实现。

    代码中注释很全,适合复习和萌新学习。这是刚入学自己写的,可能难免比不上标准的写法,但是懒得改了。

    文末会放和排序相关的基本拓展总结链接。

    看不明白可以看群里视频

    注意排序实现的具体方式,不要用局部变量,否则占空间太多,和空间复杂度不符。

    好,我们开始。

    • 选择排序

    选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在待排序序列的起始位置,直到全部待排序的数据元素排完。时间复杂度O(N^2)

    for i in range(len(l)):#意义是第i个位置开始挑第i大(小)的元素
        for j in range(i,len(l)):#和其他待排序的元素比较
    	if l[j]<l[i]:#更大就交换
    	    l[j],l[i]=l[i],l[j]
    • 冒泡排序

    冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法

    它重复地走访过要排序的元素列,一次比较两个相邻的元素,如果他们的顺序(如从大到小、首字母从A到Z)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换(一般进行n次即可,第n次一定会把第n小的元素放到正确位置)。

    这个算法的名字由来是因为越大的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。时间复杂度O(N^2)

    for i in range(len(l)-1):#下标和i无关,代表的只是第i次排序,最多需要len(l)-1次排序即可
        for j in range(len(l)-1):#遍历每一个元素
    	if l[j]<l[j+1]:#本元素比下一个元素小,就交换
    		l[j],l[j+1]=l[j+1],l[j]

     分析一下其实每次排序都会多一个元素已经确定了位置,不需要再次遍历。

    所以j循环可以改成len(l)-i-1

    时间复杂度没变。

     

    • 插入排序

    有一个已经有序的数据序列,要求在这个已经排好的数据序列中插入一个数,但要求插入后此数据序列仍然有序,这个时候就要用到一种新的排序方法——插入排序法,插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据,算法适用于少量数据的排序,时间复杂度为O(n^2)。是稳定的排序方法。

    for i in range(1,len(l)):#意义是第i个元素开始插入i之前的序列(已经有序)
        for j in range(i,0,-1):#只要比它之前的元素小就交换
    	if l[j]<l[j-1]:
    	    l[j],l[j-1]=l[j-1],l[j]
    	else:
                break#直到比前一个元素大

     

    • 归并排序

    速度仅次于快速排序,为稳定排序算法,一般用于对总体无序,但是各子项相对有序的数列

    试想:假设已经有两个有序数列,分别存放在两个数组s,r中,我们如何把他们有序的合并在一起?

    归并排序就是在重复这样的过程,首先单个元素合并为含有两个元素的数组(有序),然后这种数组再和同类数组合并为四元素数组,以此类推,直到整个数组合并完毕。

    def gg(l,ll):#合并函数
        a,b=0,0
        k=[]#用来合并的列表
        while a<len(l) and b<len(ll):#两边都非空
            if l[a]<ll[b]:
                k.append(l[a])
                a=a+1
            elif l[a]==ll[b]:a=a+1#实现去重
            else:
                k.append(ll[b])
                b=b+1
        k=k+l[a:]+ll[b:]#加上剩下的
        return k
    
    def kk(p):#分到只剩一个元素就开始合并
        if len(p)<=1:
            return p
        a=kk(p[0:len(p)//2])#不止一个元素就切片
        b=kk(p[len(p)//2:])
        return gg(a,b)#返回排好序的一部分
    l=list(map(int,input().split(" ")))
    print(kk(l))
    • 快速排序

    快速排序(Quicksort)是对冒泡排序的一种改进。

    快速排序由C. A. R. Hoare在1962年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列

    • 随机化快排

    快速排序的最坏情况基于每次划分对主元的选择。基本的快速排序选取第一个元素作为主元。这样在数组已经有序的情况下,每次划分将得到最坏的结果。比如1 2 3 4 5,每次取第一个元素,就退化为了O(N^2)。一种比较常见的优化方法是随机化算法,即随机选取一个元素作为主元。

    这种情况下虽然最坏情况仍然是O(n^2),但最坏情况不再依赖于输入数据,而是由于随机函数取值不佳。实际上,随机化快速排序得到理论最坏情况的可能性仅为1/(2^n)。所以随机化快速排序可以对于绝大多数输入数据达到O(nlogn)的期望时间复杂度

    进一步提升可以分割为三部分,即小于区,等于区,大于区,减小了递归规模,并克服了多元素相同的退化。

    def gg(a,b):
        global l
        if a>=b:#注意停止条件,我以前没加>卡了半小时
            return
        x,y=a,b
        import random#为了避免遇到基本有序序列退化,随机选点
        g=random.randint(a,b)
        l[g],l[y]=l[y],l[g]#交换选中元素和末尾元素
        while a<b:
            if l[a]>l[y]:#比目标元素大
                l[a],l[b-1]=l[b-1],l[a]#交换
                b=b-1#大于区扩大
                #注意:换过以后a不要加,因为新换过来的元素并没有判断过
            else:a=a+1#小于区扩大
        l[y],l[a]=l[a],l[y]#这时a=b
        #现在解释a和b:a的意义是小于区下一个元素
        #b是大于区的第一个元素
        gg(x,a-1)#左右分别递归
        gg(a+1,y)
    
    l=list(map(int,input().split(" ")))
    gg(0,len(l)-1)
    print(l)
    
    • 堆排序

    堆排序(HeapSort)是一树形选择排序。堆排序的特点是:在排序过程中,将R[l..n]看成是一棵完全二叉树顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系,在当前无序区中选择关键字最大(或最小)的记录。

    由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。

    堆排序是就地排序,辅助空间为O(1).

    它是不稳定的排序方法。

    主要思想:维持一个大根堆(根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最大者,称为大根堆,又称最大堆。注意:①堆中任一子树亦是堆。②以上讨论的堆实际上是二叉堆(Binary Heap),类似地可定义k叉堆。)

    第一步:通过调整原地建立大根堆

    第二步:每次交换堆顶和边界元素,并减枝,然后调整堆顶下沉到正确位置。

    def down(i,k):#在表l里的第i元素调整,k为边界
    
    #优先队列也是通过这种方式实现的
        global l
        while 2*i+2<k:#右孩子不越界
            lift,right=2*i+1,2*i+2
            m=max(l[i],l[lift],l[right])
            if m==l[i]:#不需要调
                break
            if m==l[lift]:#把最大的换上来
                l[i],l[lift]=l[lift],l[i]
                i=lift#目的节点下标更新
            else:#把最大的换上来
                l[i],l[right]=l[right],l[i]
                i=right#目的节点下标更新
        if 2*i+1<k:#判断左孩子
            if l[2*i+1]>l[i]:
                l[i],l[2*i+1]=l[2*i+1],l[i]
    
    def main():
        global l
        for j in range(1,len(l)+1):#调大根堆
            i=len(l)-j
            down(i,len(l))
        for i in range(len(l)-1,-1,-1):#排序
            l[i],l[0]=l[0],l[i]#最大和边界交换,剪枝
            down(0,i)
        print(l)
        
    l=list(map(int,input().split(" ")))
    main()
    
            
        
    
            
    
    • 桶排序

    桶排序不是基于比较的排序方法,只需对号入座。将相应的数字放进相应编号的桶即可。

    当要被排序的数组内的数值是均匀分配的时候,桶排序使用线性时间o(n)

    对于海量有范围数据十分适合,比如全国高考成绩排序,公司年龄排序等等。

    l=list(map(int,input().split(" ")))
    n=max(l)-min(l)
    p=[0]*(n+1)#为了省空间
    for i in l:
        p[i-min(l)]=1#去重排序,做标记即可
    for i in range(n):
        if p[i]==1:#判断是否出现过
            print(i+min(l),end=" ")
    • 希尔排序

    希尔排序(Shell's Sort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因D.L.Shell于1959年提出而得名。

    通过缩小有序步长来实现。

     

    def shell(arr):
     n=len(arr)#初始化步长
     h=1
     while h<n/3:
      h=3*h+1
     while h>=1:#判断,退出后就有序了。
      for i in range(h,n):
       j=i
       while j>=h and arr[j]<arr[j-h]:#判断是否交换
        arr[j], arr[j-h] = arr[j-h], arr[j]
        j-=h
      h=h//3#逐渐缩小步长
     print arr

    稳定性及时间复杂度

    排序稳定性概念:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。

    时间复杂度:时间复杂度是同一问题可用不同算法解决,而一个算法的质量优劣将影响到算法乃至程序的效率。算法分析的目的在于选择合适算法和改进算法。可以理解为和常数操作所成的一种关系(常数操作为O(1))

    空间复杂度类似。

    下面给出各类排序的对比图:

     

     

     

    • 基数排序

    因为桶排序是稳定的,基数排序就是很多次桶排序而已,按位进行桶排序即可。

    (个人认为桶排序名字不恰当,因为桶是先进后出,和稳定的算法正好反了,)

     

     

     

     

    总:

    比较排序和非比较排序

          常见的排序算法都是比较排序,非比较排序包括计数排序、桶排序和基数排序,非比较排序对数据有要求,因为数据本身包含了定位特征,所有才能不通过比较来确定元素的位置。

          比较排序的时间复杂度通常为O(n2)或者O(nlogn),比较排序的时间复杂度下界就是O(nlogn),而非比较排序的时间复杂度可以达到O(n),但是都需要额外的空间开销。

    • 若n较小(数据规模较小),插入排序或选择排序较好
    • 若数据初始状态基本有序(正序),插入、冒泡或随机快速排序为宜
    • 若n较大,则采用时间复杂度为O(nlogn)的排序方法:快速排序或堆排序
    • 快速排序是目前基于比较的排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
    • 堆排序所需的辅助空间少于快速排序,并且不会出现快速排序可能出现的最坏情况。这两种排序都是不稳定的。

     

     

     

     

     

     

     

     

     

    展开全文
  • 狂神说Vue笔记整理

    万次阅读 多人点赞 2020-12-11 09:13:04
    狂神说Vue笔记 ​ 想要成为真正的“互联网Java全栈工程师”还有很长的一段路要走,其中前端是绕不开的一门必修课。本阶段课程的主要目的就是带领Java后台程序员认识前端、了解前端、掌握前端,为实现成为“互联网...

    狂神说Vue笔记

    ​ 想要成为真正的“互联网Java全栈工程师”还有很长的一段路要走,其中前端是绕不开的一门必修课。本阶段课程的主要目的就是带领Java后台程序员认识前端、了解前端、掌握前端,为实现成为“互联网Java全栈工程师”再向前迈进一步。

    一、前端核心分析

    1.1、概述

    Soc原则:关注点分离原则

    Vue 的核心库只关注视图层,方便与第三方库或既有项目整合。

    HTML + CSS + JS : 视图 : 给用户看,刷新后台给的数据

    网络通信 : axios

    页面跳转 : vue-router

    状态管理:vuex

    Vue-UI : ICE , Element UI

    1.2、前端三要素

    • HTML(结构):超文本标记语言(Hyper Text Markup Language),决定网页的结构和内容
    • CSS(表现):层叠样式表(Cascading Style Sheets),设定网页的表现样式。
    • JavaScript(行为):是一种弱类型脚本语言,其源码不需经过编译,而是由浏览器解释运行,用于控制网页的行为

    1.3、结构层(HTML)

    太简单,略

    1.4、表现层(CSS)

    CSS层叠样式表是一门标记语言,并不是编程语言,因此不可以自定义变量,不可以引用等,换句话说就是不具备任何语法支持,它主要缺陷如下:

    • 语法不够强大,比如无法嵌套书写,导致模块化开发中需要书写很多重复的选择器;
    • 没有变量和合理的样式复用机制,使得逻辑上相关的属性值必须以字面量的形式重复输出,导致难以维护;
      这就导致了我们在工作中无端增加了许多工作量。为了解决这个问题,前端开发人员会使用一种称之为【CSS预处理器】的工具,提供CSS缺失的样式层复用机制、减少冗余代码,提高样式代码的可维护性。大大的提高了前端在样式上的开发效率。

    什么是CSS预处理器

    CSS预处理器定义了一种新的语言,其基本思想是,用一种专门的编程语言,为CSS增加了一些编程的特性,将CSS作为目标生成文件,然后开发者就只需要使用这种语言进行CSS的编码工作。转化成通俗易懂的话来说就是“用一种专门的编程语言,进行Web页面样式设计,再通过编译器转化为正常的CSS文件,以供项目使用”

    常用的CSS预处理器有哪些

    • SASS:基于Ruby ,通过服务端处理,功能强大。解析效率高。需要学习Ruby语言,上手难度高于LESS。
    • LESS:基于NodeJS,通过客户端处理,使用简单。功能比SASS简单,解析效率也低于SASS,但在实际开发中足够了,所以如果我们后台人员如果需要的话,建议使用LESS。

    1.5、行为层(JavaScript)

    JavaScript一门弱类型脚本语言,其源代码在发往客户端运行之前不需要经过编译,而是将文本格式的字符代码发送给浏览器,由浏览器解释运行。

    Native 原生JS开发

    原生JS开发,也就是让我们按照【ECMAScript】标准的开发方式,简称ES,特点是所有浏览器都支持。截至到当前,ES标准以发布如下版本:

    • ES3
    • ES4(内部,未正式发布)
    • ES5(全浏览器支持)
    • ES6(常用,当前主流版本:webpack打包成为ES5支持)
    • ES7
    • ES8
    • ES9(草案阶段)

    区别就是逐步增加新特性。
    TypeScript 微软的标准
    TypeScript是一种由微软开发的自由和开源的编程语言。它是JavaScript的一个超集, 而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。由安德斯·海尔斯伯格(C#、Delphi、TypeScript之父; .NET创立者) 主导。该语言的特点就是除了具备ES的特性之外还纳入了许多不在标准范围内的新特性,所以会导致很多浏览器不能直接支持TypeScript语法, 需要编译后(编译成JS) 才能被浏览器正确执行。

    JavaScript框架

    • JQuery:大家熟知的JavaScript库,优点就是简化了DOM操作,缺点就是DOM操作太频繁,影响前端性能;在前端眼里使用它仅仅是为了兼容IE6,7,8;
    • Angular:Google收购的前端框架,由一群Java程序员开发,其特点是将后台的MVC模式搬到了前端并增加了模块化开发的理念,与微软合作,采用了TypeScript语法开发;对后台程序员友好,对前端程序员不太友好;最大的缺点是版本迭代不合理(如1代–>2 代,除了名字,基本就是两个东西;截止发表博客时已推出了Angular6)
    • React:Facebook 出品,一款高性能的JS前端框架;特点是提出了新概念 【虚拟DOM】用于减少真实 DOM 操作,在内存中模拟 DOM操作,有效的提升了前端渲染效率;缺点是使用复杂,因为需要额外学习一门【JSX】语言;
    • Vue:一款渐进式 JavaScript 框架,所谓渐进式就是逐步实现新特性的意思,如实现模块化开发、路由、状态管理等新特性。其特点是综合了 Angular(模块化)和React(虚拟 DOM) 的优点;
    • Axios:前端通信框架;因为 Vue 的边界很明确,就是为了处理 DOM,所以并不具备通信能力,此时就需要额外使用一个通信框架与服务器交互;当然也可以直接选择使用jQuery 提供的AJAX 通信功能;

    二、前端发展史

    2.1、UI框架

    • Ant-Design:阿里巴巴出品,基于React的UI框架
    • ElementUI、iview、ice:饿了么出品,基于Vue的UI框架
    • BootStrap:Teitter推出的一个用于前端开发的开源工具包
    • AmazeUI:又叫“妹子UI”,一款HTML5跨屏前端框架

    2.2、JavaScript构建工具

    • Babel:JS编译工具,主要用于浏览器不支持的ES新特性,比如用于编译TypeScript
    • WebPack:模块打包器,主要作用就是打包、压缩、合并及按序加载

    注:以上知识点已将WebApp开发所需技能全部梳理完毕

    2.3、三端同一

    混合开发(Hybrid App)

    主要目的是实现一套代码三端统一(PC、Android:.apk、iOS:.ipa)并能够调用到设备底层硬件(如:传感器、GPS、摄像头等),打包方式主要有以下两种:

    • 云打包:HBuild -> HBuildX,DCloud 出品;API Cloud
    • 本地打包: Cordova(前身是 PhoneGap)

    微信小程序

    详见微信官网,这里就是介绍一个方便微信小程序UI开发的框架:WeUI

    2.4、后端技术

    前端人员为了方便开发也需要掌握一定的后端技术但我们Java后台人员知道后台知识体系极其庞大复杂,所以为了方便前端人员开发后台应用,就出现了Node JS这样的技术。Node JS的作者已经声称放弃Node JS(说是架构做的不好再加上笨重的node modules,可能让作者不爽了吧)开始开发全新架构的De no
    既然是后台技术,那肯定也需要框架和项目管理工具, Node JS框架及项目管理工具如下:

    • Express:Node JS框架
    • Koa:Express简化版
    • NPM:项目综合管理工具,类似于Maven
    • YARN:NPM的替代方案,类似于Maven和Gradle的关系

    2.5、主流前端框架

    Vue.js

    iView

    iview是一个强大的基于Vue的UI库, 有很多实用的基础组件比element ui的组件更丰富, 主要服务于PC界面的中后台产品。使用单文件的Vue组件化开发模式基于npm+webpack+babel开发, 支持ES 2015高质量、功能丰富友好的API, 自由灵活地使用空间。

    • 官网地址
    • Github
    • iview-admin

    备注:属于前端主流框架,选型时可考虑使用,主要特点是移动端支持较多

    Element UI

    Element是饿了么前端开源维护的Vue UI组件库, 组件齐全, 基本涵盖后台所需的所有组件,文档讲解详细, 例子也很丰富。主要用于开发PC端的页面, 是一个质量比较高的Vue UI组件库。
    ·官网地址
    ·Git hub
    ·vue-element-admin
    备注:属于前端主流框架,选型时可考虑使用,主要特点是桌面端支持较多

    ICE

    飞冰是阿里巴巴团队基于React/Angular/Vue的中后台应用解决方案, 在阿里巴巴内部, 已经有270多个来自几乎所有BU的项目在使用。飞冰包含了一条从设计端到开发端的完整链路,帮助用户快速搭建属于自己的中后台应用。

    • 官网地址。

    • Git hub

    备注:主要组件还是以React为主, 截止2019年02月17日更新博客前对Vue的支持还不太完善,目前尚处于观望阶段

    VantUI

    Vant UI是有赞前端团队基于有赞统一的规范实现的Vue组件库, 提供了-整套UI基础组件和业务组件。通过Vant, 可以快速搭建出风格统一的页面,提升开发效率。

    • 官网地址
    • Github

    AtUI

    at-ui是一款基于Vue 2.x的前端UI组件库, 主要用于快速开发PC网站产品。它提供了一套n pm+web pack+babel前端开发工作流程, CSS样式独立, 即使采用不同的框架实现都能保持统一的UI风格。
    ·官网地址
    ·Git hub

    Cube Ul

    cube-ui是滴滴团队开发的基于Vue js实现的精致移动端组件库。支持按需引入和后编译, 轻量灵活;扩展性强,可以方便地基于现有组件实现二次开发。

    • 官网地址
    • Github

    混合开发

    Flutter

    Flutter是谷歌的移动端UI框架, 可在极短的时间内构建Android和iOS上高质量的原生级应用。Flutter可与现有代码一起工作, 它被世界各地的开发者和组织使用, 并且Flutter是免费和开源的。

    • 官网地址
    • Github
      备注:Google出品, 主要特点是快速构建原生APP应用程序, 如做混合应用该框架为必选框架

    lonic

    lonic既是一个CSS框架也是一个Javascript UI库, lonic是目前最有潜力的一款HTML 5手机应用开发框架。通过SASS构建应用程序, 它提供了很多UI组件来帮助开发者开发强大的应用。它使用JavaScript MV VM框架和Angular JS/Vue来增强应用。提供数据的双向绑定, 使用它成为Web和移动开发者的共同选择。

    • 官网地址

    ·官网文档
    ·Git hub

    微信小程序

    mpvue

    mpvue是美团开发的一个使用Vue.js开发小程序的前端框架, 目前支持微信小程序、百度智能小程序,头条小程序和支付宝小程序。框架基于Vue.js, 修改了的运行时框架runtime和代码编译器compiler实现, 使其可运行在小程序环境中, 从而为小程序开发引入了Vue.js开发体验。
    ·官网地址
    ·Git hub
    备注:完备的Vue开发体验, 井且支持多平台的小程序开发, 推荐使用

    WeUI

    WeUI是一套同微信原生视觉体验一致的基础样式库, 由微信官方设计团队为微信内网页和微信小程序量身设计, 令用户的使用感知更加统一。包含button、cell、dialog、toast、article、icon等各式元素。

    • 官网地址
    • Github

    三、了解前后分离的演变史

    为什么需要前后分离

    3.1、后端为主的MVC时代

    为了降低开发的复杂度, 以后端为出发点, 比如:Struts、Spring MVC等框架的使用, 就是后端的MVC时代;
    SpringMVC流程为例:
    在这里插入图片描述

    • 发起请求到前端控制器(Dispatcher Servlet)
    • 前端控制器请求HandlerMapping查找Handler,可以根据xml配置、注解进行查找
    • 处理器映射器HandlerMapping向前端控制器返回Handler
    • 前端控制器调用处理器适配器去执行Handler
    • 处理器适配器去执行Handler
    • Handler执行完成给适配器返回ModelAndView
    • 处理器适配器向前端控制器返回ModelAndViewModelAndViewSpringMvc框架的一个底层对象,包括ModelView
    • 前端控制器请求视图解析器去进行视图解析,根据逻辑视图名解析成真正的视图(JSP)
    • 视图解析器向前端控制器返回View
    • 前端控制器进行视图渲染,视图渲染将模型数据(在ModelAndView对象中)填充到request
    • 前端控制器向用户响应结果
      优点
      MVC是一个非常好的协作模式, 能够有效降低代码的耦合度从架构上能够让开发者明白代码应该写在哪里。为了让View更纯粹, 还可以使用Thyme leaf、Frree marker等模板引擎, 使模板里无法写入Java代码, 让前后端分工更加清晰。
      缺点
    • 前端开发重度依赖开发环境,开发效率低,这种架构下,前后端协作有两种模式:
      • 第一种是前端写DEMO, 写好后, 让后端去套模板。好处是DEMO可以本地开发, 很高效。不足是还需要后端套模板,有可能套错,套完后还需要前端确定,来回沟通调整的成本比较大;
      • 另一种协作模式是前端负责浏览器端的所有开发和服务器端的View层模板开发。好处是UI相关的代码都是前端去写就好,后端不用太关注,不足就是前端开发重度绑定后端环境,环境成为影响前端开发效率的重要因素。
    • 前后端职责纠缠不清:模板引擎功能强大,依旧可以通过拿到的上下文变量来实现各种业务逻辑。这样,只要前端弱势一点,往往就会被后端要求在模板层写出不少业务代码,还有一个很大的灰色地带是Controller, 页面路由等功能本应该是前端最关注的, 但却是由后端来实现。Controller本身与Model往往也会纠缠不清,看了让人咬牙的业务代码经常会出现在Controller层。这些问题不能全归结于程序员的素养, 否则JSP就够了。
    • 对前端发挥的局限性:性能优化如果只在前端做空间非常有限,于是我们经常需要后端合作,但由于后端框架限制,我们很难使用[Comet】、【Big Pipe】等技术方案来优化性能。
      注:在这期间(2005年以前) , 包括早期的JSP、PHP可以称之为Web 1.0时代。在这里想说一句, 如果你是一名Java初学者, 请你不要再把一些陈旧的技术当回事了, 比如JSP, 因为时代在变、技术在变、什么都在变(引用扎克伯格的一句话:唯一不变的是变化本身);当我们去给大学做实训时,有些同学会认为我们没有讲什么干货,其实不然,只能说是你认知里的干货对于市场来说早就过时了而已

    3.2、基于AJAX带来的SPA时代

    时间回到2005年A OAX(Asynchronous JavaScript And XML, 异步JavaScript和XML,老技术新用法)被正式提出并开始使用CDN作为静态资源存储, 于是出现了JavaScript王者归来(在这之前JS都是用来在网页上贴狗皮膏药广告的) 的SPA(Single Page Application) 单页面应用时代。
    在这里插入图片描述
    优点
    这种模式下, **前后端的分工非常清晰, 前后端的关键协作点是AJAX接口。**看起来是如此美妙, 但回过头来看看的话, 这与JSP时代区别不大。复杂度从服务端的JSP里移到了浏览器的JavaScript,浏览器端变得很复杂。类似Spring MVC, 这个时代开始出现浏览器端的分层架构
    在这里插入图片描述
    缺点

    • 前后端接口的约定:如果后端的接口一塌糊涂,如果后端的业务模型不够稳定,那么前端开发会很痛苦;不少团队也有类似尝试,通过接口规则、接口平台等方式来做。有了和后端一起沉淀的接口规则,还可以用来模拟数据,使得前后端可以在约定接口后实现高效并行开发。
    • 前端开发的复杂度控制:SPA应用大多以功能交互型为主,JavaScript代码过十万行很正常。大量JS代码的组织,与View层的绑定等,都不是容易的事情。

    3.3、前端为主的MV*时代

    此处的MV*模式如下:

    • MVC(同步通信为主) :Model、View、Controller
    • MVP(异步通信为主) :Model、View、Presenter
    • MVVM(异步通信为主):Model、View、View Model为了降低前端开发复杂度,涌现了大量的前端框架,比如:Angular JSReactVue.jsEmber JS等, 这些框架总的原则是先按类型分层, 比如Templates、Controllers、Models, 然后再在层内做切分,如下图:

    在这里插入图片描述

    优点

    • 前后端职责很清晰:前端工作在浏览器端,后端工作在服务端。清晰的分工,可以让开发并行,测试数据的模拟不难, 前端可以本地开发。后端则可以专注于业务逻辑的处理, 输出RESTful等接口。
    • 前端开发的复杂度可控:前端代码很重,但合理的分层,让前端代码能各司其职。这一块蛮有意思的,简单如模板特性的选择,就有很多很多讲究。并非越强大越好,限制什么,留下哪些自由,代码应该如何组织,所有这一切设计,得花一本书的厚度去说明。
    • 部署相对独立:可以快速改进产品体验缺点
    • 代码不能复用。比如后端依旧需要对数据做各种校验,校验逻辑无法复用浏览器端的代码。如果可以复用,那么后端的数据校验可以相对简单化。
    • 全异步, 对SEO不利。往往还需要服务端做同步渲染的降级方案。
    • 性能并非最佳,特别是移动互联网环境下。
    • SPA不能满足所有需求, 依旧存在大量多页面应用。URL Design需要后端配合, 前端无法完全掌控。

    3.4、Node JS带来的全栈时代

    前端为主的MV*模式解决了很多很多问题, 但如上所述, 依旧存在不少不足之处。随着Node JS的兴起, JavaScript开始有能力运行在服务端。这意味着可以有一种新的研发模式:
    在这里插入图片描述
    在这种研发模式下,前后端的职责很清晰。对前端来说,两个UI层各司其职:

    • Front-end Ul layer处理浏览器层的展现逻辑。通过CSS渲染样式, 通过JavaScript添加交互功能, HTML的生成也可以放在这层, 具体看应用场景。
    • Back-end Ul layer处理路由、模板、数据获取、Cookie等。通过路由, 前端终于可以自主把控URL Design, 这样无论是单页面应用还是多页面应用, 前端都可以自由调控。后端也终于可以摆脱对展现的强关注,转而可以专心于业务逻辑层的开发。
      通过Node, WebServer层也是JavaScript代码, 这意味着部分代码可前后复用, 需要SEO的场景可以在服务端同步渲染,由于异步请求太多导致的性能问题也可以通过服务端来缓解。前一种模式的不足,通过这种模式几乎都能完美解决掉。
      与JSP模式相比, 全栈模式看起来是一种回归, 也的确是一种向原始开发模式的回归, 不过是一种螺旋上升式的回归。
      基于Node JS的全栈模式, 依旧面临很多挑战:
    • 需要前端对服务端编程有更进一步的认识。比如TCP/IP等网络知识的掌握。
    • Node JS层与Java层的高效通信。Node JS模式下, 都在服务器端, RESTful HTTP通信未必高效, 通过SOAP等方式通信更高效。一切需要在验证中前行。
    • 对部著、运维层面的熟练了解,需要更多知识点和实操经验。
    • 大量历史遗留问题如何过渡。这可能是最大最大的阻力。
      注:看到这里,相信很多同学就可以理解,为什么我总在课堂上说:“前端想学后台很难,而我们后端程序员学任何东西都很简单”;就是因为我们后端程序员具备相对完善的知识体系。
      全栈!So Easy!

    3.5、总结

    综上所述,模式也好,技术也罢,没有好坏优劣之分,只有适合不适合;前后分离的开发思想主要是基于Soc(关注度分离原则),上面种种模式,都是让前后端的职责更清晰,分工更合理高效。

    四、第一个Vue程序

    4.1、什么是MVVM

    MVVM(Model-View-ViewModel)是一种软件设计模式,由微软WPF(用于替代WinForm,以前就是用这个技术开发桌面应用程序的)和Silverlight(类似于Java Applet,简单点说就是在浏览器上运行WPF)的架构师Ken Cooper和Ted Peters开发,是一种简化用户界面的事件驱动编程方式。由John Gossman(同样也是WPF和Sliverlight的架构师)与2005年在他的博客上发表。

    MVVM源自于经典的MVC(Model-View-Controller)模式。MVVM的核心是ViewModel层,负责转换Model中的数据对象来让数据变得更容易管理和使用。其作用如下:

    • 该层向上与视图层进行双向数据绑定
    • 向下与Model层通过接口请求进行数据交互

    在这里插入图片描述

    MVVM已经相当成熟了,主要运用但不仅仅在网络应用程序开发中。当下流行的MVVM框架有Vue.jsAnfular JS

    4.2、为什么要使用MVVM

    MVVM模式和MVC模式一样,主要目的是分离视图(View)和模型(Model),有几大好处

    • 低耦合:视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的View上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
    • 可复用:你可以把一些视图逻辑放在一个ViewModel里面,让很多View重用这段视图逻辑。
    • 独立开发:开发人员可以专注于业务逻辑和数据的开发(ViewMode),设计人员可以专注于页面设计。
    • 可测试:界面素来是比较难以测试的,而现在测试可以针对ViewModel来写。
    在这里插入图片描述

    (1)View

    View是视图层, 也就是用户界面。前端主要由HTH L和csS来构建, 为了更方便地展现vi eu to del或者Hodel层的数据, 已经产生了各种各样的前后端模板语言, 比如FreeMarker,Thyme leaf等等, 各大MV VM框架如Vue.js.Angular JS, EJS等也都有自己用来构建用户界面的内置模板语言。

    (2)Model

    Model是指数据模型, 泛指后端进行的各种业务逻辑处理和数据操控, 主要围绕数据库系统展开。这里的难点主要在于需要和前端约定统一的接口规则

    (3)ViewModel

    ViewModel是由前端开发人员组织生成和维护的视图数据层。在这一层, 前端开发者对从后端获取的Model数据进行转换处理, 做二次封装, 以生成符合View层使用预期的视图数据模型。
      需要注意的是View Model所封装出来的数据模型包括视图的状态和行为两部分, 而Model层的数据模型是只包含状态的

    • 比如页面的这一块展示什么,那一块展示什么这些都属于视图状态(展示)
    • 页面加载进来时发生什么,点击这一块发生什么,这一块滚动时发生什么这些都属于视图行为(交互)

    视图状态和行为都封装在了View Model里。这样的封装使得View Model可以完整地去描述View层。由于实现了双向绑定, View Model的内容会实时展现在View层, 这是激动人心的, 因为前端开发者再也不必低效又麻烦地通过操纵DOM去更新视图。
      MVVM框架已经把最脏最累的一块做好了, 我们开发者只需要处理和维护View Model, 更新数据视图就会自动得到相应更新,真正实现事件驱动编程
      View层展现的不是Model层的数据, 而是ViewModel的数据, 由ViewModel负责与Model层交互, 这就完全解耦了View层和Model层, 这个解耦是至关重要的, 它是前后端分离方案实施的重要一环。

    4.3、Vue

    Vue(读音/vju/, 类似于view) 是一套用于构建用户界面的渐进式框架, 发布于2014年2月。与其它大型框架不同的是, Vue被设计为可以自底向上逐层应用。Vue的核心库只关注视图层, 不仅易于上手, 还便于与第三方库(如:vue-router,vue-resource,vue x) 或既有项目整合。

    (1)MVVM模式的实现者

    • Model:模型层, 在这里表示JavaScript对象
    • View:视图层, 在这里表示DOM(HTML操作的元素)
    • ViewModel:连接视图和数据的中间件, Vue.js就是MVVM中的View Model层的实现者

    在MVVM架构中, 是不允许数据和视图直接通信的, 只能通过ViewModel来通信, 而View Model就是定义了一个Observer观察者

    • ViewModel能够观察到数据的变化, 并对视图对应的内容进行更新
    • ViewModel能够监听到视图的变化, 并能够通知数据发生改变

    至此, 我们就明白了, Vue.js就是一个MV VM的实现者, 他的核心就是实现了DOM监听与数据绑定

    (2)为什么要使用Vue.js

    • 轻量级, 体积小是一个重要指标。Vue.js压缩后有只有20多kb(Angular压缩后56kb+,React压缩后44kb+)
    • 移动优先。更适合移动端, 比如移动端的Touch事件
    • 易上手,学习曲线平稳,文档齐全
    • 吸取了Angular(模块化) 和React(虚拟DOM) 的长处, 并拥有自己独特的功能,如:计算属性
    • 开源,社区活跃度高

    4.4、第一个Vue程序

    【说明】IDEA可以安装Vue的插件!
      注意:Vue不支持IE 8及以下版本, 因为Vue使用了IE 8无法模拟的ECMAScript 5特性。但它支持所有兼容ECMAScript 5的浏览器。

    (1)下载地址

    • 开发版本
      • 包含完整的警告和调试模式:https://yuejs.org/js/vue.js
      • 删除了警告, 30.96KB min+gzip:https://vuejs.org/js/vue.min.js
    • CDN
      • <script src=“https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.js”></script>
      • <script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>

    (2)代码编写

    Vue.js的核心是实现了MVVM模式, 她扮演的角色就是View Model层, 那么所谓的第一个应用程序就是展示她的数据绑定功能,操作流程如下:
      1、创建一个HTML文件

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    
    </body>
    </html>
    

    2、引入Vue.js

    <!--1.导入Vue.js-->
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
    12
    

    3、创建一个Vue实例

    <script type="text/javascript">
        var vm = new Vue({
            el:"#app",
            /*Model:数据*/
            data:{
                message:"hello,vue!"
            }
        });
    </script>
    

    说明:

    • el: '#vue':绑定元素的ID
    • data:{message:'Hello Vue!'}:数据对象中有一个名为message的属性,并设置了初始值 Hello Vue!

    4、将数据绑定到页面元素

    <!--view层,模板-->
    <div id="app">
        {{message}}
    </div>
    

    说明:只需要在绑定的元素中使用双花括号将Vue创建的名为message属性包裹起来, 即可实现数据绑定功能, 也就实现了View Model层所需的效果, 是不是和EL表达式非常像?

    (3)完整的HTML

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    
    </head>
    <body>
    
    <!--view层,模板-->
    <div id="app">
        {{message}}
    </div>
    
    <!--1.导入Vue.js-->
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
    <script type="text/javascript">
        var vm = new Vue({
            el:"#app",
            /*Model:数据*/
            data:{
                message:"hello,vue!"
            }
        });
    </script>
    </body>
    </html>
    

    (4)测试

    为了能够更直观的体验Vue带来的数据绑定功能, 我们需要在浏览器测试一番, 操作流程如下:
      1、在浏览器上运行第一个Vue应用程序, 进入开发者工具
      2、在控制台输入vm.message=‘HelloWorld’, 然后回车, 你会发现浏览器中显示的内容会直接变成HelloWorld
      此时就可以在控制台直接输入vm.message来修改值, 中间是可以省略data的, 在这个操作中, 我并没有主动操作DOM, 就让页面的内容发生了变化, 这就是借助了Vue的数据绑定功能实现的; MV VM模式中要求View Model层就是使用观察者模式来实现数据的监听与绑定, 以做到数据与视图的快速响应。

    五、基础语法指令

    4.1、v-bind

    我们已经成功创建了第一个Vue应用!看起来这跟渲染一个字符串模板非常类似, 但是Vue在背后做了大量工作。现在数据和DOM已经被建立了关联, 所有东西都是响应式的。我们在控制台操作对象属性,界面可以实时更新!
      我们还可以使用v-bind来绑定元素特性!
      上代码

    <!DOCTYPE html>
    <html lang="en" xmlns:v-bind="http://www.w3.org/1999/xhtml">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    
    </head>
    <body>
    
    <!--view层,模板-->
    <div id="app">
        <span v-bind:title="message">
        鼠标悬停几秒钟查看此处动态绑定的提示信息!
      </span>
    </div>
    
    <!--1.导入Vue.js-->
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
    <script type="text/javascript">
        var vm = new Vue({
            el:"#app",
            /*Model:数据*/
            data:{
                message: '页面加载于 ' + new Date().toLocaleString()
            }
        });
    </script>
    </body>
    </html>
    

    你看到的v-bind等被称为指令。指令带有前缀v以表示它们是Vue提供的特殊特性。可能你已经猜到了, 它们会在渲染的DOM上应用特殊的响应式行为在这里,该指令的意思是:“将这个元素节点的title特性和Vue实例的message属性保持一致”。
      如果你再次打开浏览器的JavaScript控制台, 输入app, message=‘新消息’,就会再一次看到这个绑定了title特性的HTML已经进行了更新。

    4.2、v-if, v-else

    什么是条件判断语句,就不需要我说明了吧,以下两个属性!

    • v-if
    • v-else

    上代码

    <!DOCTYPE html>
    <html lang="en" xmlns:v-bind="http://www.w3.org/1999/xhtml">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <!--view层,模板-->
    <div id="app">
        <h1 v-if="ok">Yes</h1>
        <h1 v-else>No</h1>
       
    </div>
    
    <!--1.导入Vue.js-->
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
    <script type="text/javascript">
        var vm = new Vue({
            el:"#app",
            /*Model:数据*/
            data:{
                type: true
            }
        });
    </script>
    </body>
    </html>
    

    测试:
    1.在浏览器上运行,打开控制台!
    2.在控制台输入vm.ok=false然后回车,你会发现浏览器中显示的内容会直接变成NO
      注:使用v-*属性绑定数据是不需要双花括号包裹的

    v-else-if

    • v-if
    • v-else-if
    • v-else
      注:===三个等号在JS中表示绝对等于(就是数据与类型都要相等)上代码:
    <!DOCTYPE html>
    <html lang="en" xmlns:v-bind="http://www.w3.org/1999/xhtml">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <!--view层,模板-->
    <div id="app">
        <h1 v-if="type==='A'">A</h1>
        <h1 v-else-if="type==='B'">B</h1>
        <h1 v-else-if="type==='D'">D</h1>
        <h1 v-else>C</h1>
    
    </div>
    
    <!--1.导入Vue.js-->
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
    <script type="text/javascript">
        var vm = new Vue({
            el:"#app",
            /*Model:数据*/
            data:{
                type: 'A'
            }
        });
    </script>
    </body>
    </html>
    

    4.3、v-for

    • v-for

    格式说明

    <div id="app">
        <li v-for="(item,index) in items">
            {{item.message}}---{{index}}
        </li>
    
    </div>
    123456
    

    注:items是数组,item是数组元素迭代的别名。我们之后学习的Thymeleaf模板引擎的语法和这个十分的相似!
      上代码:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <!--view层,模板-->
    <div id="app">
        <li v-for="(item,index) in items">
            {{item.message}}---{{index}}
        </li>
    
    </div>
    
    <!--1.导入Vue.js-->
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
    <script type="text/javascript">
        var vm = new Vue({
            el:"#app",
            /*Model:数据*/
            data:{
                items:[
                    {message:'狂神说Java'},
                    {message:'狂神说前端'},
                    {message:'狂神说运维'}
                ]
            }
        });
    </script>
    </body>
    </html>
    

    测试:在控制台输入vm.items.push({message:'狂神说运维'}),尝试追加一条数据,你会发现浏览器中显示的内容会增加一条狂神说运维.

    4.4、v-on

    v-on监听事件
     emsp;事件有Vue的事件、和前端页面本身的一些事件!我们这里的click是vue的事件, 可以绑定到Vue中的methods中的方法事件!
      上代码

    <!DOCTYPE html>
    <html lang="en" xmlns:v-on="http://www.w3.org/1999/xhtml">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    
    <div id="app">
        <button v-on:click="sayHi">点我</button>
    </div>
    
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        var vm = new Vue({
            el:"#app",
            data:{
                message:'Hello World'
            },
            methods:{
                sayHi:function(event){
                    //'this'在方法里面指向当前Vue实例
                    alert(this.message);
                }
            }
        });
    </script>
    </body>
    </html>
    

    点击测试
      Vue还有一些基本的使用方式, 大家有需要的可以再跟着官方文档看看, 因为这些基本的指令几乎我们都见过了,一通百通!掌握学习的方式!

    六、表单双绑、组件

    6.1、什么是双向数据绑定

    Vue.js是一个MV VM框架, 即数据双向绑定, 即当数据发生变化的时候, 视图也就发生变化, 当视图发生变化的时候,数据也会跟着同步变化。这也算是Vue.js的精髓之处了。
      值得注意的是,我们所说的数据双向绑定,一定是对于UI控件来说的非UI控件不会涉及到数据双向绑定。单向数据绑定是使用状态管理工具的前提。如果我们使用vue x那么数据流也是单项的,这时就会和双向数据绑定有冲突。

    (1)为什么要实现数据的双向绑定

    Vue.js中,如果使用vuex, 实际上数据还是单向的, 之所以说是数据双向绑定,这是用的UI控件来说, 对于我们处理表单, Vue.js的双向数据绑定用起来就特别舒服了。即两者并不互斥,在全局性数据流使用单项,方便跟踪;局部性数据流使用双向,简单易操作。

    6.2、在表单中使用双向数据绑定

    你可以用v-model指令在表单、及元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇, 但v-model本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。
      注意:v-model会忽略所有表单元素的valuecheckedselected特性的初始值而总是将Vue实例的数据作为数据来源。你应该通过JavaScript在组件的data选项中声明初始值!

    (1)单行文本

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <div id="app">
        输入的文本:<input type="text" v-model="message" value="hello">{{message}}
    </div>
    
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        var vm = new Vue({
            el:"#app",
            data:{
                message:""
            }
        });
    </script>
    </body>
    </html>
    

    (2)多行文本

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <div id="app">
       多行文本:<textarea v-model="pan"></textarea>&nbsp;&nbsp;多行文本是:{{pan}}
    </div>
    
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        var vm = new Vue({
            el:"#app",
            data:{
                message:"Hello hello!"
            }
        });
    </script>
    </body>
    </html>
    

    (3)单复选框

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    
    <div id="app">
        单复选框:
        <input type="checkbox" id="checkbox" v-model="checked">
        &nbsp;&nbsp;
        <label for="checkbox">{{checked}}</label>
    </div>
    
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        var vm = new Vue({
            el:"#app",
            data:{
                checked:false
            }
        });
    </script>
    </body>
    </html>
    

    4多复选框

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    
    <div id="app">
        多复选框:
        <input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
        &nbsp;&nbsp;
        <label for="jack">Jack</label>
        <input type="checkbox" id="join" value="Join" v-model="checkedNames">
        &nbsp;&nbsp;
        <label for="join">Jack</label>
        <input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
        &nbsp;&nbsp;
        <label for="mike">Mike</label>
        <span>选中的值:{{checkedNames}}</span>
    </div>
    
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        var vm = new Vue({
            el:"#app",
            data:{
                checkedNames:[]
            }
        });
    </script>
    </body>
    </html>
    

    (6)单选按钮

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    
    <div id="app">
        单选框按钮
        <input type="radio" id="one" value="One" v-model="picked">
        <label for="one">One</label>
        <input type="radio" id="two" value="Two" v-model="picked">
        <label for="two">Two</label>
        <span>选中的值:{{picked}}</span>
    </div>
    
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        var vm = new Vue({
            el:"#app",
            data:{
                picked:'Two'
            }
        });
    </script>
    </body>
    </html>
    

    (7)下拉框

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <div id="app">
    <!--    性别:
        <input type="radio" name="sex" value="男" v-model="pan">男
        <input type="radio" name="sex" value="女" v-model="pan">女
        <p>选中了:{{pan}}</p>-->
    
        下拉框:
        <select v-model="pan">
            <option value="" disabled>---请选择---</option>
            <option>A</option>
            <option>B</option>
            <option>C</option>
            <option>D</option>
        </select>
        <span>value:{{pan}}</span>
    
    
    
    </div>
    
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        var vm = new Vue({
            el:"#app",
            data:{
                pan:"A"
            }
        });
    </script>
    </body>
    </html>
    

    注意:v-model表达式的初始值未能匹配任何选项,元系将被渲染为“未选中”状态。 在iOS中, 这会使用户无法选择第一个选项,因为这样的情况下,iOS不会触发change事件。因此,更推荐像上面这样提供一个值为空的禁用选项。

    6.3、什么是组件

    组件是可复用的Vue实例, 说白了就是一组可以重复使用的模板, 跟JSTL的自定义标签、Thymelealth:fragment等框架有着异曲同工之妙,通常一个应用会以一棵嵌套的组件树的形式来组织:
    在这里插入图片描述
    在这里插入图片描述

    例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。

    (1)第一个Vue组件

    注意:在实际开发中,我们并不会用以下方式开发组件,而是采用vue-cli创建,vue模板文件的方式开发,以下方法只是为了让大家理解什么是组件。
      使用Vue.component()方法注册组件,格式如下:

    <div id="app">
      <pan></pan>
    </div>
    
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
    <script type="text/javascript">
        //先注册组件
        Vue.component("pan",{
            
            template:'<li>Hello</li>'
    
        });
        //再实例化Vue
        var vm = new Vue({
            el:"#app",
        });
    </script>
    

    说明:

    • Vue.component():注册组件
    • pan:自定义组件的名字
    • template:组件的模板

    (2)使用props属性传递参数

    像上面那样用组件没有任何意义,所以我们是需要传递参数到组件的,此时就需要使用props属性了!
      注意:默认规则下props属性里的值不能为大写;

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    
    <div id="app">
        <!--组件:传递给组件中的值:props-->
      <pan v-for="item in items" v-bind:panh="item"></pan>
    </div>
    
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
    <script type="text/javascript">
        //定义组件
        Vue.component("pan",{
            props:['panh'],
            template:'<li>{{panh}}</li>'
    
        });
        var vm = new Vue({
            el:"#app",
            data:{
                items:["java","Linux","前端"]
            }
        });
    </script>
    </body>
    </html>
    

    说明

    • v-for="item in items":遍历Vue实例中定义的名为items的数组,并创建同等数量的组件
    • v-bind:panh="item":将遍历的item项绑定到组件中props定义名为item属性上;= 号左边的panhprops定义的属性名,右边的为item in items 中遍历的item项的值

    七、Axios异步通信

    7.1、什么是Axios

    Axios是一个开源的可以用在浏览器端和Node JS的异步通信框架, 她的主要作用就是实现AJAX异步通信,其功能特点如下:

    • 从浏览器中创建XMLHttpRequests
    • 从node.js创建http请求
    • 支持Promise API[JS中链式编程]
    • 拦截请求和响应
    • 转换请求数据和响应数据
    • 取消请求
    • 自动转换JSON数据
    • 客户端支持防御XSRF(跨站请求伪造)

    GitHub:https://github.com/axios/axios
      中文文档:http://www.axios-js.com/

    7.2、为什么要使用Axios

    由于Vue.js是一个视图层框架并且作者(尤雨溪) 严格准守SoC(关注度分离原则)所以Vue.js并不包含AJAX的通信功能, 为了解决通信问题, 作者单独开发了一个名为vue-resource的插件, 不过在进入2.0版本以后停止了对该插件的维护并推荐了Axios框架。少用jQuery, 因为它操作Dom太频繁!

    7.3、第一个Axios应用程序

    咱们开发的接口大部分都是采用JSON格式, 可以先在项目里模拟一段JSON数据, 数据内容如下:创建一个名为data.json的文件并填入上面的内容, 放在项目的根目录下

    {
      "name": "狂神说Java",
      "url": "https://blog.kuangstudy.com",
      "page": 1,
      "isNonProfit": true,
      "address": {
        "street": "含光门",
        "city": "陕西西安",
        "country": "中国"
      },
      "links": [
        {
          "name": "bilibili",
          "url": "https://space.bilibili.com/95256449"
        },
        {
          "name": "狂神说Java",
          "url": "https://blog.kuangstudy.com"
        },
        {
          "name": "百度",
          "url": "https://www.baidu.com/"
        }
      ]
    }
    

    测试代码

    <!DOCTYPE html>
    <html lang="en" xmlns:v-binf="http://www.w3.org/1999/xhtml">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <!--v-cloak 解决闪烁问题-->
        <style>
            [v-cloak]{
                display: none;
            }
        </style>
    </head>
    <body>
    <div id="vue">
        <div>地名:{{info.name}}</div>
        <div>地址:{{info.address.country}}--{{info.address.city}}--{{info.address.street}}</div>
        <div>链接:<a v-binf:href="info.url" target="_blank">{{info.url}}</a> </div>
    </div>
    
    <!--引入js文件-->
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script type="text/javascript">
        var vm = new Vue({
            el:"#vue",
            //data:属性:vm
            data(){
                return{
                    info:{
                        name:null,
                        address:{
                            country:null,
                            city:null,
                            street:null
                        },
                        url:null
                    }
                }
            },
            mounted(){//钩子函数
                axios
                    .get('data.json')
                    .then(response=>(this.info=response.data));
            }
        });
    </script>
    </body>
    </html>
    

    说明:

    1. 在这里使用了v-bind将a:href的属性值与Vue实例中的数据进行绑定
    2. 使用axios框架的get方法请求AJAX并自动将数据封装进了Vue实例的数据对象中
    3. 我们在data中的数据结构必须和Ajax响应回来的数据格式匹配!

    7.4、Vue的生命周期

    官方文档:https://cn.vuejs.org/v2/guide/instance.html#生命周期图示
      Vue实例有一个完整的生命周期,也就是从开始创建初女台化数据、编译模板、挂载DOM、渲染一更新一渲染、卸载等一系列过程,我们称这是Vue的生命周期。通俗说就是Vue实例从创建到销毁的过程,就是生命周期。
      在Vue的整个生命周期中,它提供了一系列的事件,可以让我们在事件触发时注册JS方法,可以让我们用自己注册的JS方法控制整个大局,在这些事件响应方法中的this直接指向的是Vue的实例。
    在这里插入图片描述

    八、计算属性、内容分发、自定义事件

    1、什么是计算属性

    计算属性的重点突出在属性两个字上(属性是名词),首先它是个属性其次这个属性有计算的能力(计算是动词),这里的计算就是个函数:简单点说,它就是一个能够将计算结果缓存起来的属性(将行为转化成了静态的属性),仅此而已;可以想象为缓存!
      上代码

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <!--view层,模板-->
    <div id="app">
        <p>currentTime1:{{currentTime1()}}</p>
        <p>currentTime2:{{currentTime2}}</p>
    </div>
    
    <!--1.导入Vue.js-->
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
    <script type="text/javascript">
        var vm = new Vue({
            el:"#app",
            data:{
              message:"pan"
            },
            methods:{
                currentTime1:function(){
                    return Date.now();//返回一个时间戳
                }
            },
            computed:{
                currentTime2:function(){//计算属性:methods,computed方法名不能重名,重名之后,只会调用methods的方法
                    this.message;
                    return Date.now();//返回一个时间戳
                }
            }
        });
    </script>
    </body>
    </html>
    

    注意:methods和computed里的东西不能重名
    说明:

    • methods:定义方法, 调用方法使用currentTime1(), 需要带括号

    • computed:定义计算属性, 调用属性使用currentTime2, 不需要带括号:this.message是为了能够让currentTime2观察到数据变化而变化

    • 如何在方法中的值发生了变化,则缓存就会刷新!可以在控制台使用vm.message=”q in jiang", 改变下数据的值,再次测试观察效果!

      结论:
        调用方法时,每次都需要讲行计算,既然有计算过程则必定产生系统开销,那如果这个结果是不经常变化的呢?此时就可以考虑将这个结果缓存起来,采用计算属性可以很方便的做到这点,计算属性的主要特性就是为了将不经常变化的计算结果进行缓存,以节约我们的系统开销;

    8.2、内容分发

    Vue.js中我们使用<slot>元素作为承载分发内容的出口,作者称其为插槽,可以应用在组合组件的场景中;

    测试

    比如准备制作一个待办事项组件(todo) , 该组件由待办标题(todo-title) 和待办内容(todo-items)组成,但这三个组件又是相互独立的,该如何操作呢?
      第一步定义一个待办事项的组件

    <div id="app">
        <todo></todo>
    </div>
    <!--1.导入Vue.js-->
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
    <script type="text/javascript">
        Vue.component('todo',{
            template:'<div>\
                    <div>代办事项</div>\
                    <ul>\
                        <li>学习狂神说Java</li>\
                    </ul>\
                </div>'
        })
    </script>
    

    第二步 我们需要让,代办事项的标题和值实现动态绑定,怎么做呢?我们可以留一个插槽!
      1-将上面的代码留出一个插槽,即slot

     Vue.component('todo',{
            template:'<div>\
                    <slot name="todo-title"></slot>\
                    <ul>\
                        <slot name="todo-items"></slot>\
                    </ul>\
                </div>'
        });
    

    2-定义一个名为todo-title的待办标题组件 和 todo-items的待办内容组件

    Vue.component('todo-title',{
            props:['title'],
            template:'<div>{{title}}</div>'
        });
       
    12345
    //这里的index,就是数组的下标,使用for循环遍历的时候,可以循环出来!
        Vue.component("todo-items",{
            props:["item","index"],
            template:"<li>{{index+1}},{{item}}</li>"
        });
    

    3-实例化Vue并初始化数据

     var vm = new Vue({
            el:"#vue",
            data:{
                todoItems:['test1','test2','test3']
            }
        });
    

    4-将这些值,通过插槽插入

    <div id="vue">
        <todo>
            <todo-title slot="todo-title" title="秦老师系列课程"></todo-title>
            <!--<todo-items slot="todo-items" v-for="{item,index} in todoItems" v-bind:item="item"></todo-items>-->
            <!--如下为简写-->
            <todo-items slot="todo-items" v-for="item in todoItems" :item="item"></todo-items
        </todo>
    </div>
    

    说明:我们的todo-title和todo-items组件分别被分发到了todo组件的todo-title和todo-items插槽中
      完整代码如下:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <!--view层,模板-->
    <div id="vue">
        <todo>
            <todo-title slot="todo-title" title="title"></todo-title>
            <!--<todo-items slot="todo-items" v-for="{item,index} in todoItems" v-bind:item="item"></todo-items>-->
            <!--如下为简写-->
            <todo-items slot="todo-items" v-for="item in todoItems" :item="item"></todo-items
        </todo>
    </div>
    <!--1.导入Vue.js-->
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
    <script type="text/javascript">
        Vue.component('todo',{
            template:'<div>\
                    <slot name="todo-title"></slot>\
                    <ul>\
                        <slot name="todo-items"></slot>\
                    </ul>\
                </div>'
        });
        Vue.component('todo-title',{
            props:['title'],
            template:'<div>{{title}}</div>'
        });
        //这里的index,就是数组的下标,使用for循环遍历的时候,可以循环出来!
        Vue.component("todo-items",{
            props:["item","index"],
            template:"<li>{{index+1}},{{item}}</li>"
        });
    
        var vm = new Vue({
            el:"#vue",
            data:{
                title:"秦老师系列课程",
                todoItems:['test1','test2','test3']
            }
        });
    </script>
    </body>
    </html>
    

    8.3、自定义事件

    通以上代码不难发现,数据项在Vue的实例中, 但删除操作要在组件中完成, 那么组件如何才能删除Vue实例中的数据呢?此时就涉及到参数传递与事件分发了, Vue为我们提供了自定义事件的功能很好的帮助我们解决了这个问题; 使用this.$emit(‘自定义事件名’, 参数) , 操作过程如下:
      1-在vue的实例中增加了methods对象并定义了一个名为removeTodoltems的方法

    var vm = new Vue({
            el:"#vue",
            data:{
                title_text:"秦老师系列课程",
                todoItems:['test1','test2','test3']
            },
            methods:{
                removeItems:function(index){
                    console.log("删除了"+this.todoItems[index]+"OK");
                    //splice() 方法向/从数组中添加/删除项目,然后返回被删除的项目,其中index
                    this.todoItems.splice(index,1);
                }
            }
        });
    

    2-修改todo-items待办内容组件的代码,增加一个删除按钮,并且绑定事件!

     Vue.component("todo-items",{
            props:["item_p","index_p"],
            template:"<li>{{index_p+1}},{{item_p}} <button @click='remove'>删除</button></li>",
            methods:{
                remove:function (index) {
                //这里的remove是自定义事件名称,需要在HTML中使用v-on:remove的方式
                    //this.$emit 自定义事件分发
                    this.$emit('remove',index);
                }
            }
        });
    

    3-修改todo-items待办内容组件的HTML代码,增加一个自定义事件,比如叫remove,可以和组件的方法绑定,然后绑定到vue的方法!

    <!--增加了v-on:remove="removeTodoItems(index)"自定义事件,该组件会调用Vue实例中定义的-->
    <todo-items slot="todo-items" v-for="(item,index) in todoItems"
                        :item_p="item" :index_p="index" v-on:remove="removeItems(index)" :key="index"></todo-items>
    

    对上一个代码进行修改,实现删除功能

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <!--view层,模板-->
    <div id="vue">
        <todo>
            <todo-title slot="todo-title" :title="title_text"></todo-title>
            <!--<todo-items slot="todo-items" v-for="(item,index) in todoItems" v-bind:item="item"></todo-items>-->
            <!--如下为简写-->
            <todo-items slot="todo-items" v-for="(item,index) in todoItems"
                        :item_p="item" :index_p="index" v-on:remove="removeItems(index)" :key="index"></todo-items>
        </todo>
    </div>
    <!--1.导入Vue.js-->
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        Vue.component('todo',{
            template:'<div>\
                    <slot name="todo-title"></slot>\
                    <ul>\
                        <slot name="todo-items"></slot>\
                    </ul>\
                </div>'
        });
        Vue.component('todo-title',{
            props:['title'],
            template:'<div>{{title}}</div>'
        });
        //这里的index,就是数组的下标,使用for循环遍历的时候,可以循环出来!
        Vue.component("todo-items",{
            props:["item_p","index_p"],
            template:"<li>{{index_p+1}},{{item_p}} <button @click='remove_methods'>删除</button></li>",
            methods:{
                remove_methods:function (index) {
                    //this.$emit 自定义事件分发
                    this.$emit('remove',index);
                }
            }
        });
    
        var vm = new Vue({
            el:"#vue",
            data:{
                title_text:"秦老师系列课程",
                todoItems:['test1','test2','test3']
            },
            methods:{
                removeItems:function(index){
                    console.log("删除了"+this.todoItems[index]+"OK");
                    this.todoItems.splice(index,1);
                }
            }
        });
    </script>
    </body>
    </html>
    

    逻辑理解

    在这里插入图片描述

    8.4、Vue入门小结

    核心:数据驱动,组件化

    优点:借鉴了AngularJS的模块化开发和React的虚拟Dom,虚拟Dom就是把Demo操作放到内存中执行;

    常用的属性:

    • v-if
    • v-else-if
    • v-else
    • v-for
    • v-on绑定事件,简写@
    • v-model数据双向绑定
    • v-bind给组件绑定参数,简写:

    组件化:

    • 组合组件slot插槽
    • 组件内部绑定事件需要使用到this.$emit("事件名",参数);
    • 计算属性的特色,缓存计算数据

    遵循SoC关注度分离原则,Vue是纯粹的视图框架,并不包含,比如Ajax之类的通信功能,为了解决通信问题,我们需要使用Axios框架做异步通信;

    说明

    Vue的开发都是要基于NodeJS,实际开发采用Vue-cli脚手架开发,vue-router路由,vuex做状态管理;Vue UI,界面我们一般使用ElementUI(饿了么出品),或者ICE(阿里巴巴出品)来快速搭建前端项目~~

    官网:

    • https://element.eleme.cn/#/zh-CN
    • https://ice.work/

    九、第一个vue-cli项目

    9.1、什么是vue-cli

    vue-cli官方提供的一个脚手架,用于快速生成一个vue的项目模板;
      预先定义好的目录结构及基础代码,就好比咱们在创建Maven项目时可以选择创建一个骨架项目,这个估计项目就是脚手架,我们的开发更加的快速;
      项目的功能

    • 统一的目录结构
    • 本地调试
    • 热部署
    • 单元测试
    • 集成打包上线

    9.2、需要的环境

    • Node.js:http://nodejs.cn/download/
      安装就是无脑的下一步就好,安装在自己的环境目录下
    • Git:https://git-scm.com/doenloads
      镜像:https://npm.taobao.org/mirrors/git-for-windows/

    确认nodejs安装成功:

    • cmd下输入node -v,查看是否能够正确打印出版本号即可!
    • cmd下输入npm -v,查看是否能够正确打印出版本号即可!
      这个npm,就是一个软件包管理工具,就和linux下的apt软件安装差不多!
        安装Node.js淘宝镜像加速器(cnpm)
        这样的话,下载会快很多~
    # -g 就是全局安装
    npm install cnpm -g
    
    # 或使用如下语句解决npm速度慢的问题
    npm install --registry=https://registry.npm.taobao.org
    

    安装的过程可能有点慢~,耐心等待!虽然安装了cnpm,但是尽量少用!
      安装的位置:C:\Users\administrator\AppData\Roaming\npm

    在这里插入图片描述
      安装vue-cli

    cnpm instal1 vue-cli-g
    #测试是否安装成功#查看可以基于哪些模板创建vue应用程序,通常我们选择webpack
    vue list
    

    在这里插入图片描述

    9.3、第一个vue-cli应用程序

    1.创建一个Vue项目,我们随便建立一个空的文件夹在电脑上,我这里在D盘下新建一个目录

    D:\Project\vue-study;
    

    2.创建一个基于webpack模板的vue应用程序

    #1、首先需要进入到对应的目录 cd D:\Project\vue-study
    #2、这里的myvue是顶日名称,可以根据自己的需求起名
    vue init webpack myvue
    

    一路都选择no即可;
      说明:

    • Project name:项目名称,默认回车即可
    • Project description:项目描述,默认回车即可
    • Author:项目作者,默认回车即可
    • Install vue-router:是否安装vue-router,选择n不安装(后期需要再手动添加)
    • Use ESLint to lint your code:是否使用ESLint做代码检查,选择n不安装(后期需要再手动添加)
    • Set up unit tests:单元测试相关,选择n不安装(后期需要再手动添加)
    • Setupe2etests with Nightwatch:单元测试相关,选择n不安装(后期需要再手动添加)
    • Should we run npm install for you after the,project has been created:创建完成后直接初始化,选择n,我们手动执行;运行结果!

    (1)初始化并运行

    cd myvue
    npm install
    npm run dev
    

    执行完成后,目录多了很多依赖

    当出现问题时,可以查看提示进行处理如下
    img

    十、webpack使用

    10.1、什么是Webpack

    本质上, webpack是一个现代JavaScript应用程序的静态模块打包器(module bundler) 。当webpack处理应用程序时, 它会递归地构建一个依赖关系图(dependency graph) , 其中包含应用程序需要的每个模块, 然后将所有这些模块打包成一个或多个bundle.
      Webpack是当下最热门的前端资源模块化管理和打包工具, 它可以将许多松散耦合的模块按照依赖和规则打包成符合生产环境部署的前端资源。还可以将按需加载的模块进行代码分离,等到实际需要时再异步加载。通过loader转换, 任何形式的资源都可以当做模块, 比如Commons JS、AMD、ES 6、CSS、JSON、Coffee Script、LESS等;
      伴随着移动互联网的大潮, 当今越来越多的网站已经从网页模式进化到了WebApp模式。它们运行在现代浏览器里, 使用HTML 5、CSS 3、ES 6等新的技术来开发丰富的功能, 网页已经不仅仅是完成浏览器的基本需求; WebApp通常是一个SPA(单页面应用) , 每一个视图通过异步的方式加载,这导致页面初始化和使用过程中会加载越来越多的JS代码,这给前端的开发流程和资源组织带来了巨大挑战。
      前端开发和其他开发工作的主要区别,首先是前端基于多语言、多层次的编码和组织工作,其次前端产品的交付是基于浏览器的,这些资源是通过增量加载的方式运行到浏览器端,如何在开发环境组织好这些碎片化的代码和资源,并且保证他们在浏览器端快速、优雅的加载和更新,就需要一个模块化系统,这个理想中的模块化系统是前端工程师多年来一直探索的难题。

    10.2、模块化的演进

    Script标签

    	<script src = "module1.js"></script>
    	<script src = "module2.js"></script>
    	<script src = "module3.js"></script>
    

    这是最原始的JavaScript文件加载方式,如果把每一个文件看做是一个模块,那么他们的接口通常是暴露在全局作用域下,也就是定义在window对象中,不同模块的调用都是一个作用域。
      这种原始的加载方式暴露了一些显而易见的弊端:

    • 全局作用域下容易造成变量冲突
    • 文件只能按照<script>的书写顺序进行加载
    • 开发人员必须主观解决模块和代码库的依赖关系
    • 在大型项目中各种资源难以管理,长期积累的问题导致代码库混乱不堪

    CommonsJS


    服务器端的NodeJS遵循CommonsJS规范,该规范核心思想是允许模块通过require方法来同步加载所需依赖的其它模块,然后通过exports或module.exports来导出需要暴露的接口。

    require("module");
    require("../module.js");
    export.doStuff = function(){};
    module.exports = someValue;
    1234
    

    优点:

    • 服务器端模块便于重用
    • NPM中已经有超过45万个可以使用的模块包
    • 简单易用

    缺点:

    • 同步的模块加载方式不适合在浏览器环境中,同步意味着阻塞加载,浏览器资源是异步加载的
    • 不能非阻塞的并行加载多个模块

    实现:

    • 服务端的NodeJS
    • •Browserify,浏览器端的CommonsJS实现,可以使用NPM的模块,但是编译打包后的文件体积较大
    • modules-webmake,类似Browserify,但不如Browserify灵活
    • wreq,Browserify的前身

    AMD


    Asynchronous Module Definition规范其实主要一个主要接口define(id?,dependencies?,factory);它要在声明模块的时候指定所有的依赖dependencies,并且还要当做形参传到factory中,对于依赖的模块提前执行。

    define("module",["dep1","dep2"],functian(d1,d2){
    	return someExportedValue;
    });
    require(["module","../file.js"],function(module,file){});
    1234
    

    优点

    • 适合在浏览器环境中异步加载模块
    • 可以并行加载多个模块

    缺点

    • 提高了开发成本,代码的阅读和书写比较困难,模块定义方式的语义不畅
    • 不符合通用的模块化思维方式,是一种妥协的实现

    实现

    • RequireJS
    • curl

    CMD


    Commons Module Definition规范和AMD很相似,尽保持简单,并与CommonsJS和NodeJS的Modules规范保持了很大的兼容性。

    define(function(require,exports,module){
    	var $=require("jquery");
    	var Spinning = require("./spinning");
    	exports.doSomething = ...;
    	module.exports=...;
    });
    

    优点:

    • 依赖就近,延迟执行
    • 可以很容易在NodeJS中运行缺点
    • 依赖SPM打包,模块的加载逻辑偏重

    实现

    • Sea.js
    • coolie

    ES6模块


    EcmaScript 6标准增加了JavaScript语言层面的模块体系定义。ES 6模块的设计思想, 是尽量静态化, 使编译时就能确定模块的依赖关系, 以及输入和输出的变量。Commons JS和AMD模块,都只能在运行时确定这些东西。

    import "jquery"
    export function doStuff(){}
    module "localModule"{}
    

    优点

    • 容易进行静态分析
    • 面向未来的Ecma Script标准

    缺点

    • 原生浏览器端还没有实现该标准
    • 全新的命令,新版的Node JS才支持

    实现

    • Babel

    大家期望的模块
      系统可以兼容多种模块风格, 尽量可以利用已有的代码, 不仅仅只是JavaScript模块化, 还有CSS、图片、字体等资源也需要模块化。

    10.3、安装Webpack

    WebPack是一款模块加载器兼打包工具, 它能把各种资源, 如JS、JSX、ES 6、SASS、LESS、图片等都作为模块来处理和使用。
      安装:

    npm install webpack -g
    npm install webpack-cli -g
    

    测试安装成功

    • webpack -v
    • webpack-cli -v

    在这里插入图片描述

    配置

    创建 webpack.config.js配置文件

    • entry:入口文件, 指定Web Pack用哪个文件作为项目的入口
    • output:输出, 指定WebPack把处理完成的文件放置到指定路径
    • module:模块, 用于处理各种类型的文件
    • plugins:插件, 如:热更新、代码重用等
    • resolve:设置路径指向
    • watch:监听, 用于设置文件改动后直接打包
    module.exports = {
    	entry:"",
    	output:{
    		path:"",
    		filename:""
    	},
    	module:{
    		loaders:[
    			{test:/\.js$/,;\loade:""}
    		]
    	},
    	plugins:{},
    	resolve:{},
    	watch:true
    }
    

    直接运行webpack命令打包

    10.4、使用webpack

    1. 创建项目
    2. 创建一个名为modules的目录,用于放置JS模块等资源文件
    3. 在modules下创建模块文件,如hello.js,用于编写JS模块相关代码
    	//暴露一个方法:sayHi
    	exports.sayHi = function(){
    		document.write("<div>Hello Webpack</div>");
    	}
    
    1. 在modules下创建一个名为main.js的入口文件,用于打包时设置entry属性
    //require 导入一个模块,就可以调用这个模块中的方法了
    var hello = require("./hello");
    hello.sayHi();
    
    1. 在项目目录下创建webpack.config.js配置文件,使用webpack命令打包
    module.exports = {
    	entry:"./modules/main.js",
    	output:{
    		filename:"./js/bundle.js"
    	}
    
    }
    
    1. 在项目目录下创建HTML页面,如index.html,导入webpack打包后的JS文件
    	<!doctype html>
    	<html lang="en">
    		<head>
    			<meta charset="UTF-8">
    			<title>狂神说Java</title>
    		</head>
    		<body>
    			<script src="dist/js/bundle.js"></script>
    		</body>
    	</html>
    
    1. 在IDEA控制台中直接执行webpack;如果失败的话,就使用管理员权限运行即可!
    2. 运行HTML看效果

    说明

    # 参数--watch 用于监听变化
    webpack --watch
    

    十一、vue-router路由

    11.1、说明


    学习的时候,尽量的打开官方的文档

    Vue Router是Vue.js官方的路由管理器。它和Vue.js的核心深度集成, 让构建单页面应用变得易如反掌。包含的功能有:

    • 嵌套的路由/视图表
    • 模块化的、基于组件的路由配置
    • 路由参数、查询、通配符
    • 基于Vue js过渡系统的视图过渡效果
    • 细粒度的导航控制
    • 带有自动激活的CSS class的链接
    • HTML5 历史模式或hash模式, 在IE 9中自动降级
    • 自定义的滚动行为

    11.2、安装

    基于第一个vue-cli进行测试学习; 先查看node modules中是否存在vue-router
      vue-router是一个插件包, 所以我们还是需要用n pm/cn pm来进行安装的。打开命令行工具,进入你的项目目录,输入下面命令。

    npm install vue-router --save-dev
    

    如果在一个模块化工程中使用它,必须要通过Vue.use()明确地安装路由功能:

    import Vue from 'vue'
    import VueRouter from 'vue-router'
    
    Vue.use(VueRouter);
    

    11.3、测试

    1、先删除没有用的东西
    2、components 目录下存放我们自己编写的组件
    3、定义一个Content.vue 的组件

    <template>
    	<div>
    		<h1>内容页</h1>
    	</div>
    </template>
    
    <script>
    	export default {
    		name:"Content"
    	}
    </script>
    

    Main.vue组件

    	<template>
    	<div>
    		<h1>首页</h1>
    	</div>
    </template>
    
    <script>
    	export default {
    		name:"Main"
    	}
    </script>
    

    4、安装路由,在src目录下,新建一个文件夹:router,专门存放路由,配置路由index.js,如下

    import Vue from'vue'
    //导入路由插件
    import Router from 'vue-router'
    //导入上面定义的组件
    import Content from '../components/Content'
    import Main from '../components/Main'
    //安装路由
    Vue.use(Router) ;
    //配置路由
    export default new Router({
    	routes:[
    		{
    			//路由路径
    			path:'/content',
    			//路由名称
    			name:'content',
    			//跳转到组件
    			component:Content
    			},{
    			//路由路径
    			path:'/main',
    			//路由名称
    			name:'main',
    			//跳转到组件
    			component:Main
    			}
    		]
    	});
    

    5、在main.js中配置路由

    import Vue from 'vue'
    import App from './App'
    
    //导入上面创建的路由配置目录
    import router from './router'//自动扫描里面的路由配置
    
    //来关闭生产模式下给出的提示
    Vue.config.productionTip = false;
    
    new Vue({
    	el:"#app",
    	//配置路由
    	router,
    	components:{App},
    	template:'<App/>'
    });
    

    6、在App.vue中使用路由

    <template>
    	<div id="app">
    		<!--
    			router-link:默认会被渲染成一个<a>标签,to属性为指定链接
    			router-view:用于渲染路由匹配到的组件
    		-->
    		<router-link to="/">首页</router-link>
    		<router-link to="/content">内容</router-link>
    		<router-view></router-view>
    	</div>
    </template>
    
    <script>
    	export default{
    		name:'App'
    	}
    </script>
    <style></style>
    

    十二、实战快速上手

    我们采用实战教学模式并结合ElementUI组件库,将所需知识点应用到实际中,以最快速度带领大家掌握Vue的使用;

    12.1、创建工程

    注意:命令行都要使用管理员模式运行
    1、创建一个名为hello-vue的工程vue init webpack hello-vue
    2、安装依赖, 我们需要安装vue-router、element-ui、sass-loader和node-sass四个插件

    #进入工程目录
    cd hello-vue
    #安装vue-routern 
    npm install vue-router --save-dev
    #安装element-ui
    npm i element-ui -S
    #安装依赖
    npm install
    # 安装SASS加载器
    cnpm install sass-loader node-sass --save-dev
    #启功测试
    npm run dev
    

    3、Npm命令解释:

    • npm install moduleName:安装模块到项目目录下
    • npm install -g moduleName:-g的意思是将模块安装到全局,具体安装到磁盘哪个位置要看npm config prefix的位置
    • npm install -save moduleName:–save的意思是将模块安装到项目目录下, 并在package文件的dependencies节点写入依赖,-S为该命令的缩写
    • npm install -save-dev moduleName:–save-dev的意思是将模块安装到项目目录下,并在package文件的devDependencies节点写入依赖,-D为该命令的缩写

    12.2、创建登录页面

    把没有用的初始化东西删掉!
      在源码目录中创建如下结构:

    • assets:用于存放资源文件
    • components:用于存放Vue功能组件
    • views:用于存放Vue视图组件
    • router:用于存放vue-router配置

    在这里插入图片描述

    创建首页视图,在views目录下创建一个名为Main.vue的视图组件:

    <template>
    	<div>首页</div>
    </template>
    <script>
    	export default {
    			name:"Main"
    	}
    </script>
    <style scoped>
    </style>
    

    创建登录页视图在views目录下创建名为Login.vue的视图组件,其中el-*的元素为ElementUI组件;

    <template>
      <div>
        <el-form ref="loginForm" :model="form" :rules="rules" label-width="80px" class="login-box">
          <h3 class="login-title">欢迎登录</h3>
          <el-form-item label="账号" prop="username">
            <el-input type="text" placeholder="请输入账号" v-model="form.username"/>
          </el-form-item>
          <el-form-item label="密码" prop="password">
            <el-input type="password" placeholder="请输入密码" v-model="form.password"/>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" v-on:click="onsubmit('loginForm')">登录</el-button>
          </el-form-item>
        </el-form>
    
        <el-dialog title="温馨提示" :visible.sync="dialogVisiable" width="30%" :before-close="handleClose">
          <span>请输入账号和密码</span>
          <span slot="footer" class="dialog-footer">
              <el-button type="primary" @click="dialogVisible = false">确定</el-button>
            </span>
        </el-dialog>
      </div>
    </template>
    
    <script>
        export default {
            name: "Login",
          data(){
              return{
                form:{
                  username:'',
                  password:''
                },
                //表单验证,需要在 el-form-item 元素中增加prop属性
                rules:{
                  username:[
                    {required:true,message:"账号不可为空",trigger:"blur"}
                  ],
                  password:[
                    {required:true,message:"密码不可为空",tigger:"blur"}
                  ]
                },
    
                //对话框显示和隐藏
                dialogVisible:false
              }
          },
          methods:{
              onSubmit(formName){
                //为表单绑定验证功能
                this.$refs[formName].validate((valid)=>{
                  if(valid){
                    //使用vue-router路由到指定界面,该方式称为编程式导航
                    this.$router.push('/main');
                  }else{
                    this.dialogVisible=true;
                    return false;
                  }
                });
              }
          }
        }
    </script>
    
    <style lang="scss" scoped>
      .login-box{
        border:1px solid #DCDFE6;
        width: 350px;
        margin:180px auto;
        padding: 35px 35px 15px 35px;
        border-radius: 5px;
        -webkit-border-radius: 5px;
        -moz-border-radius: 5px;
        box-shadow: 0 0 25px #909399;
      }
      .login-title{
        text-align:center;
        margin: 0 auto 40px auto;
        color: #303133;
      }
    </style>
    

    创建路由,在router目录下创建一个名为index.js的vue-router路由配置文件

    //导入vue
    import Vue from 'vue';
    import VueRouter from 'vue-router';
    //导入组件
    import Main from "../views/Main";
    import Login from "../views/Login";
    //使用
    Vue.use(VueRouter);
    //导出
    export default new VueRouter({
      routes: [
        {
          //登录页
          path: '/main',
          component: Main
        },
        //首页
        {
          path: '/login',
          component: Login
        },
      ]
    
    })
    
    

    APP.vue

    <template>
      <div id="app">
        <router-view></router-view>
      </div>
    </template>
    
    <script>
    
    
    export default {
      name: 'App',
    
    }
    </script>
    
    <style>
    #app {
      font-family: 'Avenir', Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    </style>
    

    main.js

    // The Vue build version to load with the `import` command
    // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
    import Vue from 'vue'
    import App from './App'
    import router from "./router"
    
    import ElementUI from 'element-ui'
    import 'element-ui/lib/theme-chalk/index.css'
    
    Vue.use(router)
    Vue.use(ElementUI)
    
    /* eslint-disable no-new */
    new Vue({
      el: '#app',
      router,
      render:h=>h(App)
    })
    

    测试:在浏览器打开 http://localhost:8080/#/login
    如果出现错误: 可能是因为sass-loader的版本过高导致的编译错误,当前最高版本是8.0.2,需要退回到7.3.1 ;
    去package.json文件里面的 "sass-loader"的版本更换成7.3.1,然后重新cnpm install就可以了;

    12.3、路由嵌套

    嵌套路由又称子路由,在实际应用中,通常由多层嵌套的组件组合而成。
    demo
    1、 创建用户信息组件,在 views/user 目录下创建一个名为 Profile.vue 的视图组件;
    Profile.vue

    <template>
      <h1>个人信息</h1>
    </template>
    <script>
      export default {
        name: "UserProfile"
      }
    </script>
    <style scoped>
    </style>
    

    2、在用户列表组件在 views/user 目录下创建一个名为 List.vue 的视图组件;
    List.vue

    <template>
      <h1>用户列表</h1>
    </template>
    <script>
      export default {
        name: "UserList"
      }
    </script>
    <style scoped>
    </style>
    

    3、 修改首页视图,我们修改 Main.vue 视图组件,此处使用了 ElementUI 布局容器组件,代码如下:
    Main.vue

    <template>
        <div>
          <el-container>
            <el-aside width="200px">
              <el-menu :default-openeds="['1']">
                <el-submenu index="1">
                  <template slot="title"><i class="el-icon-caret-right"></i>用户管理</template>
                  <el-menu-item-group>
                    <el-menu-item index="1-1">
                    <!--插入的地方-->
                      <router-link to="/user/profile">个人信息</router-link>
                    </el-menu-item>
                    <el-menu-item index="1-2">
                    <!--插入的地方-->
                      <router-link to="/user/list">用户列表</router-link>
                    </el-menu-item>
                  </el-menu-item-group>
                </el-submenu>
                <el-submenu index="2">
                  <template slot="title"><i class="el-icon-caret-right"></i>内容管理</template>
                  <el-menu-item-group>
                    <el-menu-item index="2-1">分类管理</el-menu-item>
                    <el-menu-item index="2-2">内容列表</el-menu-item>
                  </el-menu-item-group>
                </el-submenu>
              </el-menu>
            </el-aside>
    
            <el-container>
              <el-header style="text-align: right; font-size: 12px">
                <el-dropdown>
                  <i class="el-icon-setting" style="margin-right: 15px"></i>
                  <el-dropdown-menu slot="dropdown">
                    <el-dropdown-item>个人信息</el-dropdown-item>
                    <el-dropdown-item>退出登录</el-dropdown-item>
                  </el-dropdown-menu>
                </el-dropdown>
              </el-header>
              <el-main>
              <!--在这里展示视图-->
                <router-view />
              </el-main>
            </el-container>
          </el-container>
        </div>
    </template>
    <script>
        export default {
            name: "Main"
        }
    </script>
    <style scoped lang="scss">
      .el-header {
        background-color: #B3C0D1;
        color: #333;
        line-height: 60px;
      }
      .el-aside {
        color: #333;
      }
    </style>
    

    4、 配置嵌套路由修改 router 目录下的 index.js 路由配置文件,使用children放入main中写入子模块,代码如下
    index.js

    //导入vue
    import Vue from 'vue';
    import VueRouter from 'vue-router';
    //导入组件
    import Main from "../views/Main";
    import Login from "../views/Login";
    //导入子模块
    import UserList from "../views/user/List";
    import UserProfile from "../views/user/Profile";
    
    //使用
    Vue.use(VueRouter);
    //导出
    export default new VueRouter({
      routes: [
        {
          //登录页
          path: '/main',
          component: Main,
          //  写入子模块
          children: [
            {
              path: '/user/profile',
              component: UserProfile,
            }, {
              path: '/user/list',
              component: UserList,
            },
          ]
        },
        //首页
        {
          path: '/login',
          component: Login
    
        },
      ]
    })
    

    5、 路由嵌套实战效果图
    在这里插入图片描述

    图 路由嵌套效果图

    12.4、参数传递

    这里演示如果请求带有参数该怎么传递
    demo
     用的还是上述例子的代码 修改一些代码 这里不放重复的代码了
    第一种取值方式
    1、 修改路由配置, 主要是router下的index.js中的 path 属性中增加了 :id 这样的占位符

    {
    	path: '/user/profile/:id', 
    	name:'UserProfile', 
    	component: UserProfile
    }
    

    2、传递参数
     此时我们在Main.vue中的route-link位置处 to 改为了 :to,是为了将这一属性当成对象使用,注意 router-link 中的 name 属性名称 一定要和 路由中的 name 属性名称 匹配,因为这样 Vue 才能找到对应的路由路径;

    <!--name是组件的名字 params是传的参数 如果要传参数的话就需要用v:bind:来绑定-->
    <router-link :to="{name:'UserProfile',params:{id:1}}">个人信息</router-link>
    

    3、在要展示的组件Profile.vue中接收参数 使用 {{$route.params.id}}来接收
    Profile.vue 部分代码

    <template>
      <!--  所有的元素必须在根节点下-->
      <div>
        <h1>个人信息</h1>
        {{$route.params.id}}
      </div>
    </template>
    

    第二种取值方式 使用props 减少耦合
    1、修改路由配置 , 主要在router下的index.js中的路由属性中增加了 props: true 属性

    {
    	path: '/user/profile/:id', 
    	name:'UserProfile', 
    	component: UserProfile, 
    	props: true
    }
    

    2、传递参数和之前一样 在Main.vue中修改route-link地址

    <!--name是组件的名字 params是传的参数 如果要传参数的话就需要用v:bind:来绑定-->
    <router-link :to="{name:'UserProfile',params:{id:1}}">个人信息</router-link>
    

    3、在Profile.vue接收参数为目标组件增加 props 属性
    Profile.vue

    <template>
      <div>
        个人信息
        {{ id }}
      </div>
    </template>
    <script>
        export default {
          props: ['id'],
          name: "UserProfile"
        }
    </script>
    <style scoped>
    </style>
    

    在这里插入图片描述

    图 传参效果图

    12.5、组件重定向

    重定向的意思大家都明白,但 Vue 中的重定向是作用在路径不同但组件相同的情况下,比如:
    在router下面index.js的配置

    {
      path: '/main',
      name: 'Main',
      component: Main
    },
    {
      path: '/goHome',
      redirect: '/main'
    }
    

    说明:这里定义了两个路径,一个是 /main ,一个是 /goHome,其中 /goHome 重定向到了 /main 路径,由此可以看出重定向不需要定义组件;

    使用的话,只需要在Main.vue设置对应路径即可;

    <el-menu-item index="1-3">
        <router-link to="/goHome">回到首页</router-link>
    </el-menu-item>
    

    12.6、路由模式与 404

    路由模式有两种

    • hash:路径带 # 符号,如 http://localhost/#/login
    • history:路径不带 # 符号,如 http://localhost/login

    修改路由配置,代码如下:

    export default new Router({
      mode: 'history',
      routes: [
      ]
    });
    

    404 demo
    1.创建一个NotFound.vue视图组件
    NotFound.vue

    <template>
        <div>
          <h1>404,你的页面走丢了</h1>
        </div>
    </template>
    <script>
        export default {
            name: "NotFound"
        }
    </script>
    <style scoped>
    </style>
    

    2.修改路由配置index.js

    import NotFound from '../views/NotFound'
    {
       path: '*',
       component: NotFound
    }
    

    3.效果图
    在这里插入图片描述

    图 404效果图

    路由钩子与异步请求

    beforeRouteEnter:在进入路由前执行
    beforeRouteLeave:在离开路由前执行

    在Profile.vue中写

      export default {
        name: "UserProfile",
        beforeRouteEnter: (to, from, next) => {
          console.log("准备进入个人信息页");
          next();
        },
        beforeRouteLeave: (to, from, next) => {
          console.log("准备离开个人信息页");
          next();
        }
      }
    

    参数说明:
    to:路由将要跳转的路径信息
    from:路径跳转前的路径信息
    next:路由的控制参数
    next() 跳入下一个页面
    next(’/path’) 改变路由的跳转方向,使其跳到另一个路由
    next(false) 返回原来的页面
    next((vm)=>{}) 仅在 beforeRouteEnter 中可用,vm 是组件实例

    在钩子函数中使用异步请求

    1、安装 Axios

    cnpm install --save vue-axios
    

    2、main.js引用 Axios

    import axios from 'axios'
    import VueAxios from 'vue-axios'
    Vue.use(VueAxios, axios)
    

    3、准备数据 : 只有我们的 static 目录下的文件是可以被访问到的,所以我们就把静态文件放入该目录下。
    数据和之前用的json数据一样 需要的去上述axios例子里

    // 静态数据存放的位置
    static/mock/data.json
    

    4.在 beforeRouteEnter 中进行异步请求
    Profile.vue

      export default {
        //第二种取值方式
        // props:['id'],
        name: "UserProfile",
        //钩子函数 过滤器
        beforeRouteEnter: (to, from, next) => {
          //加载数据
          console.log("进入路由之前")
          next(vm => {
            //进入路由之前执行getData方法
            vm.getData()
          });
        },
        beforeRouteLeave: (to, from, next) => {
          console.log("离开路由之前")
          next();
        },
        //axios
        methods: {
          getData: function () {
            this.axios({
              method: 'get',
              url: 'http://localhost:8080/static/mock/data.json'
            }).then(function (response) {
              console.log(response)
            })
          }
        }
      }
    

    5.路由钩子和axios结合图
    在这里插入图片描述

    图 效果图

    到此结束 完结★,°:.☆( ̄▽ ̄)/$:.°★

    展开全文
  • 研究生如何文献阅读笔记

    千次阅读 多人点赞 2020-07-26 10:20:47
    关于读书文献阅读笔记,这里谈点个人的看法,算是跟年轻朋友们的交流,也希望得到专家们的指教。 如何选择阅读文献? 阅读文献,要力求对一个方面或一个主题,或者一个概念的历史发展都要搞清楚,清楚...

    **

    研究生如何做文献阅读笔记

    **
    研究生如何做文献阅读笔记?
    说实在的,我自己也不是很会读书。读书的速度也不快,只是喜欢读书罢了。阅读文献,对于开题期间的研究生和写论文期间的研究生是很重要的功课,不可忽视,这种能力是需要训练的。关于做读书和文献阅读笔记,这里谈点个人的看法,算是跟年轻朋友们的交流,也希望得到专家们的指教。
    如何选择和阅读文献?

    1. 阅读文献,要力求对一个方面或一个主题,或者一个概念的历史发展都要搞清楚,清楚来龙去脉。文献有新有旧,有些学科或专题文献的半衰期很长,经典文献的阅读是很重要的,只下载几篇新文献是很难理解全貌的。
    2. 要有意识阅读大家的文献,阅读某个领域或专题中程碑式的文献或文献综述。这些文献对于初学者了解一个学科或领域的发展很有帮助,对于某个阶段的重要文献提供了一个查找的捷径。从中可以很快了解一些相关理论和学说、重要结果的进展。
    3. 要善于分析自己研究领域中一些国内外代表性实验室的论文,通过分析一个实验室的论文目录,可以了解这个实验室的发展过程和研究兴趣的发展、拓展。
    4. 要善于分析本领域一些代表性学者的论文,通过分析这些引领学科或领域发展的科学家的论文目录,同样可以看到他(她)个人研究兴趣和研究生涯的发展,以及他(她)所领导的研究团队的发展过程。
      如何做阅读笔记呢?
    5. 在广泛阅读的基础上,要善于总结和整合,类似Minireview 的方式。如果能将类似相近的一些重要文献(如 10-20 篇),进行整合和归纳,理出最新 的几个专题的进展,无疑会加深对所阅读的文献的理解。那么笔记记什么?记录新进展。哪些是新进展?需要广泛阅读才能知晓。
    6. 阅读任何文献或专著,一定要记录清楚文献题目、出处、作者、发表年代、期卷、页码等等信息,这些信息是以后引文时必须的,不要嫌麻烦,如作者栏目是需要将所有作者都要记录全的。
    7. 有些重要文献需要精读,读几遍是不行的,要很熟悉。这类文献在不同时期读有不同时期的理解,如开题阶段,可能比较注重某个方向或领域的理论和观点、实验方法和技术手段;在实验阶段,可能比较注意进行结果之间的比较,根据文献结果和变化规律,对自己的结果进行一些趋势预测;在论文写 作阶段,可能会比较关注结果分析、理论学说的验证等等。与之相应,多数文献是需要泛读的,可能只需要读读题目,可能只看看摘要,也可能只浏览一下图表等等。
    8. 要重视论文的题目和摘要,这是很重要和简洁、精炼的信息。一篇论文的精华部分都在这里了。同样文章中的一些重要信息也是需要特别关注的,如生态生理学特别关注物种对环境的适应,那么环境条件就是很重要的信息了,如物种的分类地位、生物学习性、地理分布,以及分布区的海拔、气候和植被等等。
    9. 阅读文献和专著是需要积累的,要坚持不懈,多研究和教学工作恐怕一生都要坚持阅读新文献和著作。读文献有个量变到质变的过程,阅读量大了,积累多了,需要总结的方面就多了。这样日久天长,通过知识的整合,知识框架会逐渐完善,自己肚子里的“货”就会感觉逐渐充实起来了,用和取的时候就会很自如。
    10. 从初学者到专家的转变,只要有心,只是一个时间问题。信息就是资源,知识就是信息的积累和过滤、整合。无论参加学术会议,还是讨论会,有些人说了很多,占用很多时间,但你会感觉没有多少新的信息或知识,但有些人一开口,话不多,你马上就会感觉到人家肚子里知识的储存量,激烈争论的氛围,会立即安静下来,听众会被吸引,这就是所谓的专家了。专家不是万金油。博士毕业后,都应该成为一个领域的专家。再磨练积累几年,就一定 会是名副其实的专家了。
      写了这么多,似乎还是有些空。我信奉的还是那句话:好记性不如烂笔头!该记的就要记,做学问,捷径不多。勤能补拙,书山有路勤为径嘛。还要记住“伤其十指,不如断其一指”的道理,如果兴趣
      太广,面面俱到,在信息时代,成为万金油是可能的,但要成为专家可就难了,要学会“舍”和“得”。
      研究生如何进行文献管理?!
      写论文、做研究需要查阅、下载和阅读很多文献,但是文献一旦很多,就难免失控,有时候往往需要花费半个小时去寻找一篇曾经看过的论文,花费时间也影响人的情绪,为什么不好好进行文献的管理呢?!
      下面是研究生进行文献管理的几个诀窍:
    11. 下载电子版文献时(caj,pdf,html),把文章题目粘贴为文件名。
      注意,文件名不能有特殊符号,要把 \ / : * ? <>| 以及 换行符删掉。 每次按照同样的习惯设置文件名,可以防止重复下载。
    12. 不同主题存入不同文件夹。
      文件夹的题目要简短,如:PD,LTP,PKC,NO。
    13. 看过的文献归入子文件夹,最起码要把有用的和没用的分开。
    14. 重要文献根据重要程度在文件名前加001,002,003 编号,然后按名称排列图标,最重要的文献就排在最前了。
    15. 复印或打印的文献,用打孔器(¥10-15)打孔,装入硬质文件夹(¥10-20/个)。
      哪些参考文献的“诀窍”降低你论文的质量?!
      我们经常会在参考文献的引用上耍一些小聪明,或则对参考文献不够重视,殊不知这些都会降低论文质量。
    16. 知而不引明明借鉴了同行的类似工作,却故意不引用同行的类似工作,使自己工作看上去"新颖"“领先”。实际上审稿的可能就是同行。
    17. 断章取义故意截取作者试图否定的部分来烘托自己的观点。
      3 引而不确没有认真看原文,引文错漏。
    18. 来源不实某些字句来源不可靠(比如非正式的或非学术的出版物),且不注明来源。常见于一些统计数字
    19. 盲目自引不是为了说明自己的工作与前期工作之间的关系,而是单纯为提高自己文章被引用次数
      而自引。
      研究生阅读文献要厚积薄发!
      --我的文献阅读经验
      最近一段时间,我看到大家一直在讨论看文献以及如何看文献的问题。我觉得我个人的经验是:
      1,最好不要看中文的文献。我从来没有看过中文的文献,可能有一个原因是我这个方向国内作的很少。我个人觉得中文的文献有很多漏洞甚至错误的地方,作为科普读物可能还算合格。但是作为一种参考好像是不太合适。再说,咱们毕业一般要求是要发sci,我也没有见过sci引用中文的文献的。
      2,看英文的文献不要怕难,要坚持下去。我的基础很一般,本科时候挂了好多科。英语是大一过了四级,以后再也没有拾起。刚开始的时候没有一点基础,偏偏我还是作理论的,那叫一个费劲。两三天才能看完一篇文献。我刚开始的时候坚信的“书读百遍,其意自现”,但是我后来发现是我一直在原地踏步。后来我发现我思考的结果是没有结果。于是我就再看另外的文献,就这样慢慢走来,速度越来越快。后来我发现我以前不会的东西差不多都明白了。我觉得《劝学》里面的一句话“吾尝终日而思矣,不若须臾之所学也”是多么的正确了。
      3,看文献要多多益善。我以前看到有的同学问看文献要看多少?我的回答是多多益善。试想一篇文献至少要有三两可取之处,看得多了你的水平自然就上来了。我自己从研一就开发新方向,没有什么人能帮助我,我靠的只有文献。我还记得我那半年每天至少3-5篇文献,后来略有小成。我师兄更牛--每天三篇文献。现在他才博士二年级(硕士读了两年),很多方面超过了我们老板,要知道我们老板也是973首席!他现在体系是自己找的,这半年发了两篇PRB(做物理的同学知道这个不是很容易的)、一篇JPCM,其中JPCM被评为06年100篇最佳文章之一,供全世界免费下载一年。他告诉我这些成果很多都是看文献得来的,其中包括做东西的思路和写文章的英文表述等等。
      4,要批判的看文献。随着时间的增长,文献看得越来越多,我们会发现很多文献彼此是矛盾的。很多人不知道怎么办?这个就要要求我们要批判的看文献--用审稿人的眼光看他。他有那些可取之处,哪些不好。我们也不能极其推崇一个观点,要思考一下为什么有人支持另外的观点。忘记谁说的,比较牛的科研人员是能够同时容纳两种相左观点的人。这样我们才能学到更多的东西。
      这些东西是我个人的一些经验,可能和有些人的不同。我今天把它写出来是为了让这些经验更好的服务我们。我感觉我们成功最主要的是自己的努力,毕竟有做科研天赋的人太少了。努力加上肯动脑子没有解决不了的问题--人是会思考的芦苇嘛!
      研究生如何有效阅读文献?!
      要明白此问题,我们首先需要明白为什么要查阅并阅读大量文献?!
      查阅大量外文文献,然后从文献的文献再去查找,如此往复循环,你的idea不知不觉中就被启发开来。我们老板说过,看文献,最重要,最有权威,也最有深度的是什么,就是你查找的文献的文献。
      读文献一定要注意文章后面列出的参考文献,按图索骥,就很易找到更多文献,而且被引次数越多
      的越重要!!通过搜索参考文献的作者,期刊目次找到更多该领域的文章.
      那么,我们研究生们该如何阅读文献呢?如何阅读文献最有效?!
      一、看文章时作笔记
      阅读笔记本可按不同的内容进行分类摘录,如:进展,研究方法,实验方法,研究结果等,并可加上自己的批注。对于笔记要定期总结(总结过去已经做过什么----做到心中有数;现在进展到什么程度----知彼;从中发现别人的优点和不足。预测将来的热点和发展方向----才能准确出击,找到自己的方向和目标!)。我们要着眼于将自己的成果往SCI上发,所以对一些经典的陈述,要有选择性的标记并记下来。另外,有的时候想到的思路,闪过的想法,作笔记记下来,随时查一查,可能时间久了自然就有新的看法。
      勤思考
      不单单是了解别人做了什么,还要考虑别人没做什么,或者他的实验能不能和他的结论吻合,数据可不可靠等等。用图表的方式将作者的整个逻辑画出来,逐一推敲,抱着一种挑的心态想。带着挑剔的眼神去读文献,不要盲目崇拜,有些东东自己作作,发现并不是那么回事,自己要动手,自己更要动脑。看文献中懂得抓重点,找思路。主要是学习别人的IDEA。也就是看了文献问几个问题,文章的技术突破口在哪里。比如一大堆专利讲了很多种分离方法,关键不是看它先做什么后做什么,而是想这个分离方法的依据是什么,为什么人家会想到这个方法,是不是还有其他方面的物性可以利用为分离的依据。
      多与人交流,是提升自己的极好方式
      和导师谈谈你的想法,交流一下各自所了解的所在领域某一方向的研究进展;与相关方向的牛人谈谈,对自己的启发要比看文献大的多。不仅与本领域的牛人交谈,还抓住机会与其他领域的牛人交谈,牛人的一句话,有时你读半年书都读不来的。特别是其他领域的牛人,他没准就给你一个金点子,特别是在中国,牛人一般对外行人不怎么保守。集体讨论非常必要,找几个志同道合的人一起,文献人人都有一份,每人分工读不同的文献,然后大家坐到一起。顺序开讲,互相讨论。这样,文献量是不是就成N次方增加了!!!
      还有就是采用比较阅读:
      观点相反的论文可以参照来读,品味一下双方的观点。还有就是与原著同时发表的其他专家的述评、原著发表后的读者质疑,都应当和原著一起读。比较一下,就可以看到自己的差距了。
      读论文的时候最有意思的事情是发现"一稿两投"。我的意思是不少作者把同一科研数据写成相似主题的不同论文,或是在前面的基础上又有了新的发展变化。这时,如果你的课题与此类似,你的好运也就来了。因为能通过深入比较这几篇文章的异同,发现作者(或科研小组)对同一组数据的不同看法、思路的演变,或者发现作者本想隐藏的"真正"方法。我就是在分析了同一科研小组的类似实验后,迅速发现自己实验失败的关键原因,短时间内成功完成动物模型制作的,为进一步实验打下了基础。
      做技术的要善于比较和发现,一些技术含量高文献,不可能把要点都报道出来,中文如此,英文也是如此。比如一篇专利中有很多的Sample,而每个的条件或配方都不同,这时要多比较几个同类文献,看其共同点在那里。这点在制药和表面活性剂行业还是要注意的。
      全面参考国外文献。一定要清楚,国外文献也有一些不可信的文章;另外一种现象就是关于重点的关键的东西他会略去不写,有时一些细节的东西他也不会写的。问题是各人省略的关键和细节不尽相同,你便从对比中发现他们研究的脉络和问题的关键所在。
      单篇文章阅读顺序及侧重
      论文阅读顺序:
    20. 摘要 引文 引用的主要信息,研究背景。
    21. 图表 了解主要数据和解释。
    22. 讨论和结论 将图表和结论联系起来,根据图表判断结论是否恰当。
    23. 结果详细阅读结果,看数据是如何得到的,又是如何分析的。
    24. 材料和方法 详细阅读材料和实验方法,看实验是如何进行的。
    25. 讨论和结果 进一步掌握论文,注意讨论中的关于从已知的知识和研究如何解释本文获得的结果。
      另外对于论文中大量的图表来说,当你能够重新画出这张图,并且能用自己的语言解说这张图,表明就读懂了。
      一篇论文中最重要的部分依次是: 图表,讨论,文字结果,方法。现在生命科学中的杂志对图表的要求都很高,必须做到仅通过阅读图表及其说明文字即能把握文章的方法、结果,再结合读者自己的原有知识,就大概知道其implication了。这符合现代人必须在最短的时间内把握最必要的信息的要求。因此,在某个领域做了一段工作后,定期查新得到的文章只须看摘要、图表即可, 个别涉及新方法或突破性结果,再看讨论,文字结果和方法。这也提示我们在写外文文章时,注重图表及其说明文字,做到形象化、信息最大化。
      自己熟悉的领域:最省事的是只看摘要,因为依靠背景知识通过摘要即可大致勾勒出文章内容,但有时这是不够的,相对省事的方法是细看摘要,略读前言,再看结果中的图表,最后读一下自己感兴趣的讨论部分。但如果文章对自己很有意义,那就应该通读全文了。
      如在寻找课题阶段,重点读讨论和结论以及展望,在课题设计阶段,主要是材料和方法。若只需了解一下该研究的思路,可选取摘要及引文与结论进行泛读。个人的经验是尽量去把握作者的研究思路,然后是学习他们的分析方法,最后是学习写作技巧和写作语言方式等。
      文章的讨论部分真是很重要,如果时间稍微充裕点,建议研读和模仿牛人paper的讨论部分。不同的人对同样的数据可能有不同看法和分析方式,图表的趋势解析,论据的组合,都是非常看功力的部分,我们老板经常说:如果某篇SCI级别的文章让我们这些菜鸟来写,可能发国内核心都非常困难。
      我觉的最重要的是理解讨论中的精髓,这是作者idea创新性以及与旧有的实验结果比较的关键部分,可以看出作者设计此实验的思路,在作出比较以后,对自己的课题会有很大启发。
      除了文章内容,还要学习人家写作的方法和格式等等,比如同样一个观点,别人有可能表达的很地道,同样一个图表,别人做的很漂亮,尤其是老外的文章,给老外投稿,人家的修改意见要求文字通俗易懂,带有一定的科普性,即使不是本专业的人,也能大致看懂;然后还要求多用简单句,能用简单句说明的问题,就不要用复合句,在同一句子中,最好不要让同一个词汇或短语重复出现 … 最后想说的一点,就是注意中文和外文的互相印证,注意一些专业词汇的翻译,注意用词的恰当和简洁,久而久之,对于提高自己的外语水平,也是大有裨益的。
      文献追踪的重要性
      在现在这个信息时代,往往你的idea别人也会有不谋而和的时候,所以要特别关注这个领域的最新动向。在抓紧使自己出成果的同时,随时根据有可能出现的"撞车"进行调整,做到心中有数。和自己课题相关的文章一定要勤跟踪,现在国外的科研做得又快又漂亮,我们在做到心中有数的情况下,可以扬长避短,作出新东西来。了解与自己研究方向有关的机构,密切关注在该研究领域和方向的顶尖group(研究团体以及牛人)所发表的论文。对于数据库的定题、定词地定期搜索,这样才能保证你不丢下每一篇重要的文献。文献总要紧密结合自己的方向为方向服务!
      已定课题的实施
      1、得到一个大概方向。
      2、查相关中文综述,查看国内有谁或哪个单位在做相关内容。
      3、查外文综述,比较一下,毕竟外文可能会更详尽一些,看看大家对什么感兴趣。
      4、查较关键的参考文献,注意杂志和作者的权威性、引用次数
      5、重检相关全文,注意研究方法、和技术路线,讨论中存在什么问题
      6、根据本人所能控制的资金和本地技术资源考虑我能做什么,怎么做
      7、再进一步紧缩范围,有一个框架图
      8、根据框架图再进一步查外文原文以明细节。
      最后我们介绍一下试验和文献的关系:
      实验思路永远要走在实验之前,凡事想好再作,一定没错!
      在实验方案的设计和实验细节方面一定要多下功夫,力求用实验室最成熟的技术.对于一些自己没有做过的实验,一定要吃透原理,再下手不迟,切记盲目.有些实验若自己实验室确有困难,可以考虑合作,因为一个人不可能在短时间内把什么都做好.我的体会是,有时就需要请教专家!
      研究生如何进行科技文献的检索与分析
      期刊和专利部分研究生如何进行科技文献的检索与分析-期刊和专利部分
      科技文献检索是每个研究生的必备素质,需要每一个研究人员牢固掌握并零活应用!
      本文就将介绍如何进行期刊和专利的检索和分析!
      第一部分
      修改了一下标题,因为写着突然发现很多会涉及到分析部分。其实检索只是一个基础,只是为了得到一个结果,便于分析。刚才好奇,用“专利分析”在园子里面检索了一番,结果发现,讨论的很少,连相关的介绍也很少,不过发现还有位兄弟对Patent Map很有研究,而且对INAS系统有研究,所以,会在后面增加一些专利分析软件的介绍。所有的介绍均是个人研究得到的,研究的目的、用途不一样,得到的结论肯定有所差别。仅是个人意见,可以讨论。
      前人讲了很多检索,很多*****(在此说一句,够用就行了,不要浪费时间了)。但是很少有人讲检索以后该怎么办,大家默认的就是读。从读开始,然后作研究。每个人都想有所创新。但是,该如何创新呢?
      目前中国的SCI发表量与日俱增,说明了中国的研发实力正在迅速上升。但是,中国的SCI收录量和中国这样一个大国还是很不成比例,中国目前的创新太少了,基本上都是跟着别人走。为什么大家都在做这个东西,别人就能想得到,我们想不到呢?我最初感觉上是我们没有合适的方法,没有合适的工具来帮助我们。但是,日子一天一天的混,从一穷二白到也有了些资源(学校的),后来突然发现,这些东西不缺了,咋还不行呢。想想自己的工作流程,突然意识到有了合适的工具并不代表你能够创新,要学会利用。
      怎么充分利用这些工具,更好的帮助我们创新呢?好像园子里面没有人探讨,每天说的基本上都是要进入哪里取得什么东西,甚至有人说检索板块已经没有什么意义了,关掉算了,总感觉像是19世纪汤姆生关于物理学的评价,很怪异。真正的检索是为你的工作、学习、研究提供有效服务,现在的状态是只取得数据,继而就是读,怎样对这数据更好的分析,以更有效的方式来进行科研,不知道有多少人在做呢?
      目前的科学研究,可能会有很多人同时在做一个项目,等待幸运的苹果突然光顾一下你的脑袋,这种几率已经很少了,毕竟全球那么多的人口在那摆着。每天狂看很多文献,也不见得有用。很多科技信息不是你一拍脑袋就能想出来的。因此,你可以借助已经针对这些问题开发出来的一些工具去看看除了你所关心的内容之外,还有什么与你很相关的。看看这项技术的发展历程是什么样的,从开始是什么状态,慢慢演变到目前这种状态,有什么分支,别人是怎么想到的,他们的想法对我们有什么启迪作用?是不是又是一个good Idea呢?(感觉像人类进化史。)因此,这篇帖子的重点在于介绍一些工具软件来帮助你更加有效的工作和研究。
      本文不会涉及到任何***,仅是检索。以下均为个人见解,如果有不满者,欢迎讨论。
      文献有很多种,以下只考虑科技期刊和专利,原因就是上述两种文献基本覆盖了所有的技术。由于浩如烟海的文献资料,一般人很难检索全。因此,有人专门进行了研究,发现,20%的期刊汇集了足够的信息以全面反映科技的最新最重要的成果与进展。因此只要对20%的期刊进行检索,一般就能够得到你所需要的数据。由于每篇文献要对前人的技术做出相应的回报,需要在其文献中提及前人所发表的文献,即参考文献,或是引证文献,这就方便检索人员可以追踪一项技术的发展历史。有上述两方面原因,我们在做检索的时候,就会更加容易的找到我们所需要的资料。
      为什么要进行文献检索?很多人的解释是为了研发。但是屁股决定脑袋,处于什么环境的人考虑的问题是不一样的。学校的很多机构作研发,需要了解最新的,现在的,过去的技术。商业机构可能还要了解技术是否有投资必要,是否有专利侵权?而对于专利审查员来讲,就是,目前审查的专利是否已经有文献可以否定或是影响其新颖性。对于专利而言,很多公司除了研发之外还需要追踪竞争对手动向,是否有侵权行为等等。
      目前,针对上面的不同的需求有不同的产品以满足其需求。例如,科技期刊,有SCI,CA,GOOGLE,还有很多全文数据库;对于专利,有各国免费网站,DELPHION,AUREKA, DIALOG,STN,QUESTEL-ORBIT等等。
      在科技文献中,目前最权威的数据库是WOK,即web of knowledge,国内很多大学都买了,其中最牛的数据库就是WEB OF SCIENCE,国内更多的是简称为SCI。而在专利领域内,最权威的就是Derwent World Patents Index数据库,即德温特世界专利索引数据库。在专利领域内的人,如果不知道Derwent,那我就没有办法了,加紧学习吧。
      对于SCI检索,已经有很多实际的例子,精美的PPT,都在帮助我们如何进行检索。比如说张帆老师的PPT,深入浅出,非常有用。但是,对于培训,毕竟时间有限,很多细节问题没有讲到,我就啰嗦几句,如果有钻石,翡翠之类的就尽管砸过来。不对的地方呢,欢迎讨论,以下仅供参考:
      SCI是基于引证文献创建的。因此,引证文献在SCI中占有非常重要的地位。我们在对一个新的课题作检索的时候,通常会碰到一个问题,就是关键词描述不清或是描述不全,对于这种情况,引证就会发挥很大的作用。先用一个自己认为最恰当的概念或是关键词进行预检索,而后选择自己最想关的记录察看,进而根据引证文献找到更加恰当的。在SCI中,在检索结果列表的右下方会出现一个analyze按钮,以及在每条记录都有三个按钮。
      这几个按钮是非常有用的,可以帮助你更有效的进行检索,也可以帮助你对你的检索结果进行一个分析,在这,我会提到一些,就是如何帮助你进行科技创新。
      先说analyze。大家可能都用过,可以对检索结果进行统计分析。它可以对作者,国家,文献类型,语言,机构,年份,文献所属学科等等进行分类。这个功能非常的有用,比如说,你经过一番检索,假设你检索得到的数据比较准确,你可以先用文献所属学科进行分析一下,然后看看你的检索文献主要分布在那些学科领域,然后再将你感兴趣的纪录按照年份统计一下,就可以看出这个方向研究的大概走势,是上升了还是下降了?应该能够给你一个参考。或是你可以先作一个年份统计,然后再查看学科,作一个大概的分析,也能得到一个相应的结果。当然,这些分析都是基于你有一个非常准确的检索结果。Rubbish in, Rubbish out. 摸索摸索,SCI会给你一个意外的惊喜。这个功能帮助使用者Driving the Innovation.
      下面就会涉及到另一个问题,就是如何进行准确检索。检索无非就是:keyword?search?Result?modify Keyword?search?result这样一个循环过程。简单解释一下,就是先用一些你觉得最可以描述你所需要文献的关键词,然后进行逻辑组配,进行预检索,而后察看检索结果,是否有垃圾,是否有没有用上的同义词,是否有别的缩写。当你找到恰当的文献时,上面的三个按钮开始起作用了。利用其 Cited References找到以前的与你想关的文献,Times cited,可以找到这项技术的后续发展,而Find Related Records则可以找到与你关心内容相关的一些研究方向,看看,肯定会对你的研究起一个很大的提示。
      这个功能也是帮助使用者Driving The innovation.
      其实里面的功能很多,充分挖掘,充分利用,SCI这个工具不会让你失望的。
      太晚了,下次写专利相关的。
      第二部分
      对于专利这块,将会介绍很多与专利相关得重要数据库及其特色,希望对大家的工作学习有所帮助,我会尽可能得客观评价数据库和软件,可能会有一些个人偏好,请大家见谅。
      到专利这块,就比科技期刊复杂一些了。因为专利毕竟是法律文件,受法律保护,有严格的格式。同样,在专利领域中,检索专利的目的有很多种。做研发的,想了解目前的技术情况,看是否有人已经申请了专利;自己能否在被人的基础上加以改进,以绕过别人的专利保护圈;可能是为了进行专利战略分析,寻找新的发展机会;可能是为了寻找侵权者;无形资产评估等等方面。
      不同的人对于检索专利有不同的要求。因此,目前在专利市场上有免费的数据库,例如各个国家的知识产权局,有收费的专利数据库,例如Delphion, Aureka,Dialog,STN,Questl-Orbit,Micropatent的PatentWeb等等。收费有收费的好处。免费的东西毕竟还是有些问题的,例如EP的专利检索,大家都喜欢到欧洲专利局去检索,但是去那里检索主要两个问题,
      第一:检索系统太糟糕,检索的结果有很多垃圾。为什么呢,在EP网站上检索,对于主题检索而言,可利用的字段就是标题和文摘,而很多专利的标题和文摘并不能完全反映专利技术的重点,因此在做检索的时候,会产生很大的漏检或是垃圾;
      第二,有500篇限制,即V2 和V3的区别。这些都极大的限制了用户对专利的获取。可能有人这时就会出来说,美国就很好啊。确实,这点我们要承认,毕竟美国有钱,做出来的东西确实不一样。而且还曾经获过大奖。但是有一点是没有错的,就是在专利申请时,为了避免被竞争对手发现自己的技术路线,会尽可能的将自己的专利用非常模糊的语言去改写,从而达到避免被人检索出来的目的。因
      此,就算在美国这么好的地方,在商用上,还是有问题。在这,收费数据库有了市场。毕竟在商业社会,免费的东西有那么多的问题,用的让人太不放心了。比较好的收费数据库主要是指那些对专利经过深加工的数据库。专利数据领域内很有名的就是Thomson公司的 Derwent数据库,针对所有的专利,覆盖了全球几十个国家和地区专利,是世界上最好的专利数据库,还有美国的CA,主要针对化学,法国的 PharmPat药物数据库,中国在这方面也有一些比较不错的,例如国家知识产权出版社作的中医药数据库,北京东方灵盾科技有限公司作的世界传统医药数据库(好像还没有上市,网上有介绍)。
      下面,我重点介绍一些Derwent数据库一些很好的特性。这个数据库在Delphion,WOK(即在Web of Knowledge数据库中的DII),DIALOG(Dialogweb, Dialogone, Dialogclassic),STN(web和easy),QUESTEL-ORBIT。上述几个数据库,对于不同检索能力的用户可以选择不同的数据库,一般而言,DIALOGclassic,STNWEb,QUESTEL-ORBIT(这个国内用的人很少,偶也没有用过,无法评论),适合于专业的检索人员,用指令式检索。而Delphion,DII,Dialogweb则适合各个层次的用户。Dialogon,Stneasy则适合初学者。
      Derwent对每条专利记录的标题和文摘都重新进行了改写,用更加简明易懂的语言,便于检索人员能够检索到这条记录。此外,Derwent还增加了很多字段,有申请人代码,Derwent手工代码(MC),Derwent分类(DC)等等。申请人代码非常有用,是Derwent汇集了世界上专利申请量很大的大公司,并对其进行分类得到的代码表。你可以在下面这个网址进行在线查询你所需要的申请人代码http://scientific.thomson.com/support/patents/dwpiref/reftools/companycodes/lookup/。,例如,IBM有很多分公司,其代码是IBMC,通过查找就可以得到如下IBM公司的列表:
      IBMC CIE IBM FRANCE
      IBMC IBM BRASIL IND MAQUINAS & SERVICOS LTDA
      IBMC IBM CANADA LTD
      IBMC IBM CORP
      IBMC IBM DEUT GMBH
      IBMC IBM DEUT INFORMATIONSSYSTEME GMBH
      IBMC IBM INST MIKROTECHNIK MAINZ GMBH
      IBMC IBM INT BUSINESS MACHINES CORP
      IBMC IBM ISRAEL LTD
      IBMC IBM JAPAN LTD
      IBMC IBM KK
      IBMC IBM NEDERLAND NV
      IBMC IBM OESTERREICH
      IBMC IBM PATENT OPERATION
      IBMC IBM SEMEA SPA
      IBMC IBM SEMEA SRL
      IBMC IBM SVENSKA AB
      IBMC IBM UK LTD
      IBMC INT BUSINESS MACHINES CORP
      如果在普通数据库中进行检索,很难保证一次性就把所有的IBM公司的专利检索全,但是你用Derwent的公司代码表,即可尽大可能的保证你的检索精度。
      Derwent手工代码(MC)则可以看作是专利领域中的IPC。IPC过几天就升级到第8版了。但是出发点不一样,一个是从功能角度,一个是从应用角度。举一个简单例子(引自《中外专利数据库检索指南》P199-203),现在要检索一个课题,名称是:一种装置,在录像机上能根据用户眼睛凝视的方向自动对物体定位(自动聚焦)。用正常的方法,关键词,自动对焦,录像机?检索结果有很多垃圾,在这个例子中用关键词很难描述这个装置,但是在IPC中也没有什么合适的分类可以利用。这时可以
      考虑用Derwent手工代码,在初检索中所有与录像机测距和聚焦有关的记录相关的代码是W04-M01D2C,而 S05-D01C5A则包含了非医疗用物体的电气或电子测量,可用于检索依靠手指按压控制或依靠飞行员眼睛运动以控制飞机的方面的课题。将上述两个代码进行逻辑组配,即得到了非常相关的结果。从这个例子可以看出,用Derwent手工代码可以得到一个很满意的结果。
      在专利检索中,检索的思路和科技文献大致相同。但是由于专利的一些特点,其检索的自由度可能会更大些。在这,假设你已经对专利有了解了,不懂就去看书吧。
      偶的一般做法,了解课题,看需要检索什么,而后就开始初检,找到一些很合适的纪录,找IPC,MC,DC,到处撒网,然后找到一些合适的,同时也排出一些垃圾,再次进行检索,有的时候还会用上一些统计分析方法,找到最合适的MC,DC。这些步骤可能来回好几次,最终才能确定一个很好的检索式。有人觉得很麻烦,但是如果你的数据是用来做分析的时候,就非常有必要的。Rubbish in,Rubbish Out。
      在Derwent中还有一个很好的功能就是引证专利检索。可以找到一篇专利所有引证他人或是被引证的数据。这个功能好在,可以看到那些公司引证了这篇专利,你就大概可以做出如下判断:
      1、 技术是否从这篇专利所有人那里流失,即别人在上面作了很多开发,改进;
      2、 是否后来人有授权可能性;
      3、 技术走向,这时可以参考IPC,MC,DC等等。
      专利引证这个在专利评估领域非常复杂,可以写本书了,偶也研究不深,不敢乱发言,到此为止。
      这些引证数据可在Delphion和Aureka中以非常直观的图形显示,回头贴一些图上来。
      在Delphion还有一些其它功能可以帮助你更好的检索和分析数据。
      例如文本聚类,还有一个统计功能。这些回头贴图。
      在Aureka中,专利检索,分析,管理的功能更强大。可以做出非常好的文本聚类分析,形成直观的专利地图,很强。太强了。
      to be continued
      第三部分
      每次说IPC的时候都会忘记提一点,就是:由于IPC是一个世界性的标准,因此每个国家的专利审查员在对IPC的理解有不同,因此,一篇专利在不同的国家可能会得到不同的IPC分类,这就给专利检索带来很大的难度。但是Derwent的手工代码和分类则不同,只有几百个人在用,因此,在分类上有很大的统一性。
      在分类上,通常大家都知道IPC,U.S. Class, ECLA,还有Derwent 的MC和DC,但是还有一个和IPC相关的好东西,不知道大家知道否,就是Catchword。在CA里就有这个,很好用的一个对照表吧。它的意思是对 IPC分类作加工,用一个词可以检索出分布在哪些IPC中。
      例如:
      ABATTOIRS
      building aspects of ABATTOIRS
      E04H 5/00
      E04B
      E04C
      E04D
      E04F
      E04G
      equipment for ABATTOIRS
      A22B
      后记:文献检索是研究人员的基础能力,但是该能力的掌握程度,将直接影响他的研究能力,更世俗的说,他能不能成为专家和学者,跟他的文献检索能力息息相关,这里我们希望各位研究生朋友们认真阅读上面的文章,并能够认真体会!
      国内外学术资源搜索办法分析这个暑假为了准备毕业论文,在网上很努力的搜索资源,但还是有很多资源无法使用,虽然说师大的图书馆可以连接中国知网资料库(CNIK),可是,搜索到的文章总是说“对不起,贵单位没有订购”。同时,由于选题的关系,我想研究的内容的文献在国内相当稀少,怎么也找不到满意的,这让我不得不转向国外学术资源,但由于英文水平有限,加上以前没有搜索经验,所以也是没有收获。经历了几天的一无所获后,我决定先学习怎样搜索资源,因为它就是打开资源库的钥齿,没有这把钥齿再多的资源也无法获取。在网上查找了很多资料,并且经过自己不断印证练习后,我逐步开始对网上学术资源的搜索有了新的认识,现在我就将我所学到的总结一下,也希望对面临同样问题的同学有点帮助。
      本文将根据不同的搜索工具特点进行介绍。
      一.Google 搜索引擎 www.google.com
      Google 开发出了世界上最大的搜索引擎,提供了最便捷的网上信息查询方法。通过对 30 多亿网页进行整理,Google 可为世界各地的用户提供适需的搜索结果,而且搜索时间通常不到半秒。现在,Google 每天需要提供 2 亿次查询服务。Google 是由英文单词“googol”变化而来。“googol”是美国数学家 Edward Kasner 的侄子 Milton Sirotta 创造的一个词,表示 1 后边带有 100 个零的数字。Google 使用这个词代表公司想征服网上无穷无尽资料的雄心。
      1.先介绍如何使用Google进行国外论文搜索
      从网上找到的国外论文大部分是pdf格式。所以,细心一点会发现,在google搜索的文献旁边都有一个[pdf]字样,因此尝试用“key words" +“pdf” 的模式搜索国外文献,效果很好! 但是需要注意的是在搜索器默认状态下,它会同时搜索国内的中文资源,这样搜索出来的东西浏览很不方便,所以我们可以先在“使用偏好”上选择语言,只在英文上打钩,然后“保存使用偏好”,这样就可以省略掉不必要的国内部分。比如,我查找国外心理健康教育的文献,输入 “Mental health education pdf “,结果举例:
      [PDF] MENTAL HEALTH
      [PDF] Mental Health and Mass Violence
      [PDF] Promoting Mental Health
      等等。。就这样,我就找到了很多原版文献。但是这种方法盲目性较大,准确率底。
      2.其次要介绍如何利用Google搜资源库的进入帐号和密码
      用 " password+journal” 方法搜,效果也很明显。有人提出一个号称通吃天下文献数据库的密码万能的公式,password=welcome+(X),x 可以为任何一个文献数据库的名称,可以写成
      password=welcome+ProQuest
      password=welcome+Ingenta
      password=welcome+EBSCO
      等等,放到google里后,检索为 “password welcome ProQuest”这样会有好多的密码出现,据说这个方法是一个叫 Hmongbook 的人概括出来的。另外,并不是所有的数据库登陆都是username password,比如英国economist周刊,他的检索关键词就应该为: “password e-mail “,这需要各位的细心了!
      上述一种常用方法,下面是网友提供的检索常用搜索词的一些例子,模式上基本大同小异
      medicine journal ID pw
      chemWEB.COM PASSWORD
      Virtuelle Bibliothek PASSWORD
      “Online Full Text Resources password”
      “health sciences library password ”
      “OvidLWW password”
      “medizin bibliothek password”
      “medizin Volltext password”
      “medizin literatur password”
      “health ejournals password ”
      “medizin elektronik password”
      medicina BIBLIOTECA password
      médecine PéRIODIQUES éLECTRONIQUES password
      health ejournals password
      American Journal of Medicine OnLine FULL TEXT Journals username password
      同时,如果你足够细心,你会在这样的检索中有很多以外的收获。国外有很多密码页,上面公布很多期刊数据库的密码和登陆方式如果GOOGLE在检索的页面上出现 " PSAAWORD” 字样很多,那就说明这是一个密码页。你可以保存起来,说不定今后会用到他们。
      3.如何用GOOGLE快速查找图书馆试用资源
      在GOOGLE的搜索栏里输入:(试用图书数据库) inurl:lib
      8月20日测试结果
      Electronic Library, Open University of Hong Kong
      www.lib.ouhk.edu.hk/ - 14k - 网页快照 - 类似网页
      HKIEd Library Home
      Library Catalogue. Quick search:. Keywords / Phrase, AUTHOR, TITLE, SUBJECT, CALL No. ISBN / ISSN. Go to: Library Catalogue Main Page | Other Library Catalogues | HKALL View: Circulation Record / Renewal | MyLibrary | New Materials List …
      www.lib.ied.edu.hk/ - 22k - 网页快照 - 类似网页
      通过这种方法可以快速找到各高校图书馆的免费资源了。这些密码虽然持久不了,但是足够解燃眉之急。这里想说明一点,百度查找中文的期刊还是很管用,因为百度中文页面更新很快,而GOOGLE在中文方面就不是很在行了,但是英文网页,GOOGLE则是一个星期更新一次,频率较快,优先采用检索国外数据!但是GOOGLE有些朋友不是很喜欢用,原因在于进入页面以后不容易查找关键词,这里我推荐几个可以看到GOOGLE快照的网站,同学们今后可以用这些网站进行GOOGLE的搜索,很方便,容易看到关键词,不仅有利于文献的查找.还适合其他检索!
      http://www.usao.edu/search.htm
      http://www.google.com/custom
      http://www.google.com/intl/zh-CN/
      http://www.soople.com/ (各种检索已经设计好了!)
      二. Ixquick 搜索引擎 www.ixquick.com
      严格意义上讲Ixquick不是搜索引擎,是连接搜索引擎和网络用户的信息立交桥。但是对于大多数国内用户来说,Ixquick还很陌生。Ixquick众多独特的功能我不一一介绍了,只介绍最关心的,搜索数据库密码。
      使用方法:
      先进入Ixquick http://www.ixquick.com,以“Proquest”数据库为例(是世界著名的学位论文数据库,收录有欧美1,000余所大学文、理、工、农、医等领域的博士、硕士学位论文,是学术研究中十分重要的信息资源。本数据库为PQDD数据库中部分记录的全文)。和google一样,我们同样要在“my settings ”我的设置中把搜索语言设置成为英文,这样就可以避免开不必要的国内资源,保存设置后,在搜索栏填入Proquest Username Password History Online后点击search,看看出来的结果,第一页中第4.5.6个(8月20日搜索结果),proquest的username和password赫然在目,同样的方式我们可以搜索到 EBSCO、Electric Library Elementary、Electric Library Elementary、ProQuest Platinum (in school)、ProQuest Platinum (remote)等众多数据库的密码,都有uesrname和password,随便试一下EBSCO,OK,成功登陆。
      当然,由于我也是刚刚开始使用Ixquick,有关的检索关键词的选择等还有待于进一步的研究和筛选。
      三.个人主页和著作文章搜索引擎
      在http://www.informatik.uni-trier.de/~ley/db/indices/a-tree/index.html中 ,搜索专家们的著作文章主页,比如和我教育相关的斯腾伯格(jsternberg)我们输入他的英文名称jsternberg就会出来
      Search Results for ‘sternberg’
      ? K. Sternberg
      ? Michael Sternberg
      ? Michael J. E. Sternberg
      ? Paul Sternberg
      ? Robert J. Sternberg
      ? S. R. Sternberg
      ? Stanley R. Sternberg
      ? Ulrich Sternberg
      ? Norman von Sternberg-Gospos
      ? K. Sternberger
      ? Ludovic Sternberger
      我们仔细看就知道我们要找的的是第五个,打开就可以找到很多和他相关的资料了。但是因为国内其实已经有很多人搜索过他们的个人主页,所以我们如果想减少麻烦,可以直接在baidu上搜索,一般都可以找到 ,比如说在百度中输入“斯腾伯格个人主页” ,显示结果是:斯腾伯格的个人主页[心理论坛] – Powered By …
      标题:斯腾伯格的个人主页 eduxin 头衔:心灵大使等级:管理员 威望:100文章:2251 积分:4158 门派:心理学研究方法…秒, 4 次数据查询 斯腾伯格的个人主页[心理论坛] – Powered By Dvbbs.net,2006-5-29 11:…
      psychology.zjnu.net.cn/bbs/dispbbs.asp?bo … 33K 2006-5-29 - 百度快照
      打开以后我们就可以找到地址了。
      三.yisou 搜索引擎(易搜雅虎)www.yiso.com
      一搜,yahoo的新的中文搜索引擎。 界面类似于google,可以搜索中文和外文全文数据,我也是刚刚使用。仅仅用 “cnkikw”这个cnki常用密码进行搜索,然后搜索到的资源还是很多,至少感觉比百度强。就是缺少快照。但是也不错!他的功能还要进一步挖掘!
      四. goole 搜索引擎www.goole.com
      goole.com,与google比较了一下发现,能搜索到一些google搜索不到的好东东 。它界面简洁,功能强大,速度快,YAHOO、网易都采用了它的搜索技术。但对于学术资料的收集,我还没研究出什
      么特点。提出的目的只是作为另一个辅助搜索工具使用。
      五. Looksmart 搜索引擎 www.findarticles.com
      一个检索免费paper的好工具,进入网页以后,可以看到他有三个功能,driectory web article ,其中article对我们有些帮助,你可以尝试输入你要找的文章。
      六.中国知网www.cnki.net
      国家知识基础设施(National Knowledge Infrastructure,CNKI)的概念,由世界银行提出于1998年。CNKI工程是以实现全社会知识资源传播共享与增值利用为目标的信息化建设项目,由清华大学、清华同方发起,始建于1999年6月。在党和国家领导以及教育部、中宣部、科技部、新闻出版总署、国家版权局、国家计委的大力支持下,在全国学术界、教育界、出版界、图书情报界等社会各界的密切配合和清华大学的直接领导下,CNKI工程集团经过多年努力,采用自主开发并具有国际领先水平的数字图书馆技术,建成了世界上全文信息量规模最大的"CNKI数字图书馆”,并正式启动建设《中国知识资源总库》及CNKI网格资源共享平台,通过产业化运作,为全社会知识资源高效共享提供最丰富的知识信息资源和最有效的知识传播与数字化学习平台。
      CNKI工程的具体目标,一是大规模集成整合知识信息资源,整体提高资源的综合和增值利用价值;二是建设知识资源互联网传播扩散与增值服务平台,为全社会提供资源共享、数字化学习、知识创新信息化条件;三是建设知识资源的深度开发利用平台,为社会各方面提供知识管理与知识服务的信息化手段;四是为知识资源生产出版部门创造互联网出版发行的市场环境与商业机制,大力促进文化出版事业、产业的现代化建设与跨越式发展。
      提供检索的数据库主要有:
      中国期刊全文数据库 期刊/杂志 7626 1979-2006 18613520 7493
      中国优秀博硕士学位论文全文数据库 学位论文 377 1999-2006 321501 6
      中国重要会议论文全文数据库 会议论文 1018 1999-2006 456316 50
      中国重要报纸全文数据库 报纸 1000 2000-2006 5771880 6125
      中国图书全文数据库 图书 300 1949-2006 12998
      中国年鉴全文数据库 年鉴 850 1912-2006 2934233
      中国引文数据库 综合 1979-2006 3598930 1065
      在学校内我们都可以免费通过学校图书馆的连接使用该平台的资源,但正如我开头提到的,这里面的有些资源我们学校没有购买,所以我们无法使用,这就不得不让我想办法弄别的帐号和密码,但因为失效性关系,网上容易搜到的帐号常常是无法使用,这就给我们找到有用帐号增加了难度。所以只有通过大量的尝试登陆也许我们才会找到能用的。
      七.万方数据库 www.wanfangdata.com.cn
      万方数据系统汇集科研机构、科技成果、科技名人、中外标准、政策法规等近百种数据库资源,信息总量达1100多万条,每年数据更新60万条以上,为广大师生提供丰富科技信息。
      万方数据系统将数据库分为五个子系统:学位论文全文、会议论文全文、数字化期刊、科技信息、商务信息。
      帐号搜集办法是一样的,除了用中文搜索外,我们也可以用搜索国外帐号的办法来搜索 比如“xcnki password”等等
      八.附录:
      下面是一些2006年8月20号我自己测试可以使用的资源库密码和帐号
      1.西方五大外文学术期刊数据库
      NO1.EBSCO数据库介绍
      数据库简介
      EBSCO数据库包括Newspaper Source、World Magazine Bank、EBSCO Animals、Academic Search Premier、Business Source Premier、Regional Business News、MEDLINE、ERIC、Professional Development Collection等数据库,内容涉及自然科学、社会科学等各学科。其中,含生物医学全文期刊590余种。
      地址:http://search.epnet.com/
      User Name: Provcath
      Password: Library
      User ID: S8694191
      Password: Password
      下面我就对该平台进行测试
      登陆进入后单击 Full Text Magazine and Journal Articles;
      在所有选项上打勾,既选用所有数据库;
      点continue继续;
      搜索心理健康教育“Mental health education”;
      得到包括如下第一条的共243条信息:
      MENTAL HEALTH OF YOUNG PEOPLE. By: Fooks, Susan. Family Matters, Autumn98 Issue 49, p58-58, 1/6p; Abstract: The article presents information about the publication “Mental Health Education in Australian Secondary Schools: An Audit Prepared for the Mental Health Branch, Department of Health and Family Services by the Youth Research Centre and Centre for Social Health.” This Mental Health Audit was prepared for the Mental Health Branch, Department of Health and Family Services and conducted in response to a consultancy brief that sought proposals to provide an audit and assessment of educational programs concerning mental health issues that currently exist for schools in Australia and which will be used to guide the development of an education strategy to he launched as part of the Mental Health Community Awareness Program. Reading Level (Lexile): 800; (AN 748726)
      Notes: This title is not held locally
      HTML Full Text PDF Full Text (126K)
      测试成功!
      NO2. PQDD数据库介绍
      数据库简介
      PQDD (ProQuest Digital Dissertations) 是美国ProQuest Information and Learning公司(前UMI公司)出版的博硕士论文数据库,是DAO (Dissertation Abstracts Ondisc)的网络版。它收录了欧美1000余所大学的160多万篇学位论文,是目前世界上最大和最广泛使用的学位论文数据库。内容覆盖理工和人文社科等广泛领域。与光盘版相比,PQDD具有以下特点:
    26. 收录年代长,从1861年开始;
    27. 更新快,每周更新;
    28. 数据库中收录1980年以后出版的博士论文作者本人撰写的长达350字的文摘,1988年以后出版的硕士论文信息中150字的文摘,1997年以来的部分论文还可以看到前24页的论文原文。地址:http://proquest.umi.com/pqdweb
      User ID: 07B86QPQ22
      Password: Welcome
      下面我就对该平台进行测试
      登陆进入后 在学术期刊上打勾
      搜索心理健康教育“Mental health education”
      显示:1930 篇文档已找到,检索范围:Mental health education
      Increased antidepressant use and fewer suicides in J?mtland county, Sweden, after a primary care educational programme on the treatment of depression
      S. Henriksson, G. Isacsson. Acta Psychiatrica Scandinavica. Oxford: Sep 2006. Vol. 114, Iss. 3; p. 159
      测试成功!需要特别说明的是,ie版本必须是6.0以上,同时点搜索后常常出现页面无法显示的问题,这时候大家只要继续刷新页面,搜索结果就会出来了。
      下面的数据库我只进行了登陆测试,没有进行搜索测试,一般来说上面两个数据库查询本科毕业论文资料已经足够了
      NO3. http://sks.sirs.com/
      Customer: IL1394H
      Password: 60451
      First Search: Articles from newspapers, magazines, journals, ERIC and more.
      NO4. http://elibrary.bigchalk.com/
      User Name: 68-13313
      Password: bigchalk
      Oxford Reference: 100 well-known and trusted dictionaries and reference books, plus an Encyclopedia; everything from General Reference, Language and Quotations to Science and Medicine, and from Humanities and Social Sciences to Business and Professional.
      NO5. http://infotrac.galegroup.com/itweb/new71776
      Password: new_log
      2.其他一些测试过的国内外资源库帐号和密码
      NO1.万方数据库硕博论文全文下载方法
      http://218.69.114.37/wf/cddb/cddbft.htm
      点击论文后,在页面的左边是论文的章节提要,随便选中一节(一页)单击右键使用网际快车或迅雷下载软件,选择“下载全部连接”就可以下载全文了。
      下面我就对该平台进行测试
      打开网页后在关键词一栏输入“心理健康教育”;
      在所有选项上打勾,既选用所有数据库;
      点检索;
      得到当前数据库: 命中记录:81条 检索串: (心理健康教育
      1 班主任与中学生心理健康教育(全文);硕士;湖南师范大学;20050301
      2 重庆市专科院校德育现状调查及对策研究(全文);硕士;西南师范大学;20050401
      3 学校心理健康教育课程评价模型构建(全文);硕士;扬州大学;20050501
      4 贫困大学生心理健康教育研究(全文);硕士;武汉大学;20050501
      5 贫困大学生心理健康教育研究(全文);硕士;武汉大学;20050501测试成功!
      共计81篇论文。测试成功!
      NO2.cnki帐户(包硕博)
      地址: http://www.cnki.net
      帐户:sylnsz/sylnsz
      范围:中国期刊全文数据库
      中国优秀博硕士学位论文全文数据库
      中国医院知识CHKD全库
      测试说明:这个帐户因为用的人比较多,部分数据库一直无法使用。
      NO3.中国医学数字图书馆
      http://www.chkd.cnki.net/newchkd/index.asp
      用户名:hnzmyy 密码:hnzmy
      测试说明:这个帐户可以完全使用,但可惜资料多是医学的。
      NO4.美国Gavilan 大学
      http://ezproxy.gavilan.edu/login 密码:25055
      测试说明:可以正常使用所有资源。
      NO5.国务院发展研究中心信息网
      http://www.drcnet.com.cn/DRCNET.Channel.Web/
      用户名/密码: gdjwgov / gdjwgov
      测试结果:欢迎您 广东省发展计划委
      NO6.一个图书馆的后门
      资源超多,上万本各类图书免费下载!
      希望尽量不要用p2p软件下载,好资源来之不易啊,里面东西爆多,各种方面的都有。
      http://lib.cqie.cn/ebook3/
      附录九 .以下资源我就没有测试了,如有需要大家可以自己测试
      http://proxy2.lib.umanitoba.ca/login
      ID:6796696 mm:6696
      http://ezproxy.nwtc.edu:2048/login
      20000143
      13057028
      https://mutex.gmu.edu/menu 美国 George Mason University
      G00024802
      石家庄邮电职业技术学院
      http://www.drcnet.com.cn/DRCNET.Channel.Web/
      bg837/1025
      北华大学超星
      http://59.72.128.31/bookhtm/index.asp
      1140本超星书籍直接点击下载
      http://brucewen.anyp.cn/050427003021046.aspx
      检索界面的超星,可读可下载03年以前的
      http://www.lib.ecust.edu.cn/querybookx.aspx
      同济大学图书馆
      http://lib.tongji.edu.cn/services/courseware/index.htm
      清华大学资源
      http://www.lib.tsinghua.edu.cn/database/database_portal.html
      中国医院数字图书馆
      http://www.chkd.cnki.net/newchkd/index.asp
      gz0022/gzd
      集美大学图书馆(部分资源需要代理才能看)
      http://lib.jmu.edu.cn/
      新增万方资源300G,暑期免费开放.
      国家科技图书文献中心
      http://www.nstl.gov.cn/htm/qwwx/index.jsp
      中经网
      http://210.33.28.52/index/index.asp
      万方部分库
      http://www.garden.sh.cn:85/
      龙源期刊入口
      http://cn.qikan.com/neiep/
      http://cn.qikan.com/sdsglgbxy/
      期刊影响因子查询(2005年SCIE杂志影响因子)
      http://www.elmo.net.cn/OurServ/JCR/index.asp
      http://periodical.cnpeak.com/oclcpsp/index.asp
      Authorization:ECO0096
      Password: 36538
      http://search.epnet.com/login.asp
      abrhs / 01720
      http://newfirstsearch.oclc.org
      Authorization: 100-196-819
      Password: RYE8PRXNG
    展开全文
  • 即便如此,印象笔记的模板在日常的使用中也确实有其作用,恰当的使用模板能起到事半功倍的作用,因为印象笔记的编辑排版功能有时会让人感到力不从心。阿理自己的颇受欢迎的模板一提到印象笔记的模版,很多人都觉得这...
  • 读计算机专业买什么笔记本电脑好?你算问对人了

    万次阅读 多人点赞 2021-08-13 15:30:58
    整体性能优,可以完美的适合计算机专业 大数据推荐 大数据主要是对数据分析处理。利用Python语言 Hadoop、Spark 等工具对网络上的数据进行爬取,过滤、分析等。在海量数据进行清洗、处理分析,并找到其中的规律...
  • Mysql个人学习笔记

    万次阅读 多人点赞 2019-09-04 14:35:57
    好处:1、隐藏了实现细节 2、提高代码的重用性 调用:select 函数名(实参列表) 【from 表】; 特点: ①叫什么(函数名) ②干什么(函数功能) 分类: #一、字符函数 #1.length 获取参数值的字节个数 SELECT ...
  • 计算机组成原理:最详细笔记

    万次阅读 多人点赞 2021-01-21 14:26:44
    参考:《王道计算机组成原理》学习笔记总目录+思维导图 2019 王道考研 计算机组成原理 第一章 计算机系统概述 1.1 计算机发展历程 1.1.1 计算机硬件的发展 计算机系统=硬件+软件 计算机硬件的发展: 第一代计算机...
  • 数据库系统概论完整笔记

    千次阅读 2022-04-13 15:40:49
    数据库系统概论完整版,跟随尚硅谷+数据库系统概论总结的笔记
  • 那么系统自动更新有什么好处和坏处呢?工具/原料电脑方法/步骤开启电脑系统的自动更新,可以让电脑保持最新系统,可以修复已知的系统漏洞,提高系统的安全性,避免因因系统漏洞而造成的损失。开启电脑系统的自动更新...
  • mysql必知必会读书笔记

    千次阅读 多人点赞 2022-04-19 18:41:07
    本文纯粹就是小杰为了更加全面的了解学习mysql的读书笔记, 小杰觉得这本书写的相当不错, 感兴趣的朋友可以去读阅。 小杰在文末也会附上它的pdf的云盘地址, 大家一起学习,共同进步, 知识不分高低, 计算机的学习小杰...
  • 《王道》数据结构笔记整理2022

    万次阅读 多人点赞 2021-08-06 15:17:51
    1.数据:数据是信息的载体,是描述客观事物属性的数、字符以及所有能输入到计算机中并被程序识别处理的符号的集合。 2.数据元素:数据元素是数据的基本单位,通常作为一个整体进行考虑处理。一个数据元素可由...
  • 其背后的逻辑很简单,不断地快速响应、探索、挖掘、引领用户的需求、才是企业得以生存持续发展的关键因素。 这过程中使用了三丰云提供的免费云服务器。程序测试的服务器空间是三丰云提供,三丰云可以提供.
  • 计算机网络 笔记整理

    千次阅读 2022-03-01 16:42:44
    一、计算机网络的概述 互联网的两个特点:连通...为边缘部分提供服务,提供连通性数据交换(分组交换) 端到端之间通信:(计算机之间的通信)运行在不同的计算机的程序之间的通讯(通过网络) 两种通信方式..
  • 电子电路学习笔记(6)——电阻的作用

    千次阅读 多人点赞 2021-08-29 21:02:51
    一、电阻的作用 根据电子系统三要素:源,回路,电阻;这三者必须都存在才能传递电信号。如果没有电阻,那么这条回路的电流将会无穷大(电流等于电压除于电阻,如果电阻趋近于0,电流则趋近于无穷大),很容易烧坏...
  • 尚硅谷JAVA基础笔记吐血整理

    千次阅读 多人点赞 2021-12-14 19:53:54
    尚硅谷JAVA基础课程笔记整理(从面向对象开始)
  • vue学习笔记(超详细)

    万次阅读 多人点赞 2020-12-18 16:38:35
    文章目录一. Vue基础认识Vue.jsVue安装方式Vue的MVVM二. Vue基础语法生命周期模板语法创建...computed计算属性settergettercomputed / methods区别fulters过滤器三. 组件化开发组件的基本介绍注册组件的基本步骤全局
  • GELU激活函数介绍和笔记

    千次阅读 2022-01-26 04:02:24
    GELU激活函数的介绍个人笔记
  • 特征降维带来的好处是可以减少参数量减少计算量。 空洞卷积 空洞卷积(atrous convolutions)又名为扩张卷积(dilated convolitions),向卷积层引入了一个成为“扩张率(dilation rate)”的新参数,该参数定义...
  • HTML CSS整理笔记

    万次阅读 多人点赞 2019-10-26 18:50:37
    (1)B:first-child 作为父元素的第一个子元素B,作用和(3)相似 (2)B:last-child 作为父元素的最后一个子元素B (3)A B:nth-child(n) 在父级中查第n个子元素是不是B,不分类型 (4)B:first-of-type 选择父元素内B类型的...
  • @SpringCloud+RabbitMQ+...先确立思路,在order模块中的pojo实体类封装了userIduser,可以通过userId到数据库中查询到该user的数据并封装,最后返回该订单信息 实现步骤: 第一步:创建RestTemplate并注入Spring容器
  • JavaWeb笔记

    千次阅读 多人点赞 2019-09-06 10:42:23
    Javabean是一个Java类,拥有特殊的用途,自己的封装方式 通过getset对业务数据进行封装 二、JavaBean的作用? 1、封装业务逻辑 2、减少代码,代码的重复使用 三、JavaBean所拥有的特征? 1、是Java类 2、get...
  • 电脑为什么要分区,分区的好处

    千次阅读 2021-07-18 03:58:20
    您可能感兴趣的话题:分区核心提示:今天小编为大家介绍一下电脑为什么需要分区,分区有什么好处。今天小编为大家介绍一下电脑为什么需要分区,分区有什么好处。一般笔记本买回来时都只有一个C盘。1、当系统需要还原...
  • 文章目录一、1x1卷积核介绍二、举例说明实际作用例子1:线性变换例子2:降低通道数(归纳整理进行一次输入通道间的非线性映射)三、1x1卷积核作用降低/提升通道数增加非线性跨通道信息交互(channal 的变换)四、1x1...
  • kafka学习笔记

    千次阅读 2022-04-21 12:33:58
    kafka学习笔记
  • Spring整理笔记(狂神说Spring整理笔记

    千次阅读 多人点赞 2021-01-26 22:06:25
    Spring整理笔记(狂神说Spring整理笔记)1.Spring1.1简介1.2 优点1.3 组成2.IOC2.1 IOC组成理论推导2.2 IOC本质2.3 IOC创建对象的方式3.Spring的基础配置3.1 别名3.2 Bean的配置3.3 import4.DI依赖注入4.1 构造器...
  • 面试笔记~

    万次阅读 2021-02-23 14:33:50
    最新更新时间2021.2.23,刚刚...equals==区别? A: ==是判断两个变量或实例是不是指向同一个内存空间,equals是判断两个变量或实例所指向的内存空间的值是不是相同 B: ==是指对内存地址进行比较 , equals()是对
  • 笔记本有没有必要加内存条?

    千次阅读 2020-05-10 00:13:21
    因为笔记本的特殊性,大部分笔记本CPU、显卡主板是焊死在一起的,甚至有的连硬盘内存都是,这就不像台式机可以完全分开更换,也就意味着,你要换笔记本CPU就要把主板、显卡也换了,成本太高,而且前提还要是能...
  • 笔记集视频、课件、代码而成,较为详细,建议收藏关注,持续更新中,也可以看coderwhy公众号发的文章一起学习 Vue3+TS系统学习一 - 邂逅Vue3TypeScript 之前那个Vue2的笔记我把课件搞成文字太费时间了,这里直接...
  • 狂神说docker(最全笔记

    万次阅读 多人点赞 2021-03-22 12:03:51
    一.Docker入门 1. Docker 为什么会出现 2. Docker的历史 ...3.Docker最新超详细版教程通俗易懂 ...4. 虚拟化技术容器化... 传统虚拟机, 虚拟出一条硬件,运行一个完整的操作系统,然后在这个系统上安装运行软.

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 60,716
精华内容 24,286
热门标签
关键字:

做笔记的好处和作用