代码改变世界

数据结构和算法

2018-11-18 11:51  ZHSwater  阅读(252)  评论(0)    收藏  举报

最近面试web前端开发工程师。被问到了数据结构和算法,以前以为这个对前端开发师不重要,结果听了面试官的一席话,受益匪浅,所以决定好好研究一下数据结构和算法。在这里做一下总结,方便自己和他人查看。我所总结的数据结构和算法是基于JavaScript数组来说明的,如果对前台脚本js不熟悉的同学,请自行查看学习,我这里就不阐述了。

 

数据结构-数组


数组是计算机编程世界里最常见的数据结构。任何一种编程语言都包含数组,只是形式上 略有不同罢了。数组是编程语言中的内建类型,通常效率很高,可以满足不同需求的数据存储。

1.创建数组

  最简单的方式是通过 [] 操作符声明一个数组变量:

    var numbers = [];

  使用这种方式创建数组,你将得到一个长度为 0 的空数组。可以通过调用内建的 length 属 性来验证这一点:

    console.log(numbers.length); // 显示 0

  另一种方式是在声明数组变量时,直接在 [] 操作符内放入一组元素:

    var numbers = [1,2,3,4,5];

  还可以调用 Array 的构造函数创建数组:

    var numbers = new Array();

  同样,可以为构造函数传入一组元素作为数组的初始值:

    var numbers = new Array(1,2,3,4,5);

  可以调用 Array.isArray() 来判断一个对象是否是数组,如下所示:

    var numbers = 3;

     var arr = [1,2,3];

    console.log(Array.isArray(numbers)); // 显示 false

    console.log(Array.isArray(arr)); // 显示 true

  哪种方式最好?大多数 JavaScript 专家推荐使用 [] 操作符,和使用 Array 的构造函数相比,这种方式被认为效率更高

2.读写数组

  在一条赋值语句中,可以使用 [] 操作符将数据赋给数组,比如下面的循环,将 1~100 的 数字赋给一个数组:

    var nums = [];

     for (var i = 0; i < 100; ++i) {

      nums[i] = i+1;

     }

  还可以使用 [] 操作符读取数组中的元素,如下所示:

     var numbers = [1,2,3,4,5];

     var sum = numbers[0] + numbers[1] + numbers[2] + numbers[3] + numbers[4];

     console.log(sum); // 显示 15

  如果要依次读取数组中的所有元素,使用 for 循环无疑会更简单:

    var numbers = [1,2,3,5,8,13,21];

    var sum = 0;

     for (var i = 0; i < numbers.length; ++i) {

       sum += numbers[i];

     }

    console.log(sum); // 显示 53

3.由字符串生成数组

  调用字符串对象的 split() 方法也可以生成数组。该方法通过一些常见的分隔符,比如分 隔单词的空格,将一个字符串分成几部分,并将每部分作为一个元素保存于一个新建的数 组中。

  下面的这一小段程序演示了 split() 方法的工作原理:

  var sentence = "I was a chinese";

     var words = sentence.split(" ");

   for (var i = 0; i < words.length; ++i) {

     console.log("word " + i + ": " + words[i]);

   }

  该程序的输出为:

  word 0: I

  word 1: was

  word 2: a

  word 3: chinese

4.对数组的整体操作

  有几个操作是将数组作为一个整体进行的。首先,可以将一个数组赋给另外一个数组:

  var nums = [];

  for (var i = 0; i < 10; ++i) {

    nums[i] = i+1;

   }

   var samenums = nums;

  但是,当把一个数组赋给另外一个数组时,只是为被赋值的数组增加了一个新的引用。当 你通过原引用修改了数组的值,另外一个引用也会感知到这个变化。下面的代码展示了这种情况:

  var nums = [];

  for (var i = 0; i < 100; ++i) {

    nums[i] = i+1;

   }

   var samenums = nums;

   nums[0] = 400;

      console.log(samenums[0]); // 显示 400

  这种行为被称为浅复制,新数组依然指向原来的数组。一个更好的方案是使用深复制,将 原数组中的每一个元素都复制一份到新数组中。可以写一个深复制函数来做这件事:

  function copy(arr1, arr2) {

    for (var i = 0; i < arr1.length; ++i) {

       arr2[i] = arr1[i];

       }

   }

  这样,下述代码片段的输出就和我们希望的一样了:

  var nums = [];

  for (var i = 0; i < 100; ++i) {

    nums[i] = i+1;

   }

  var samenums = [];

  copy(nums, samenums);

  nums[0] = 400;

     console.log(samenums[0]); // 显示 1

5.查找元素

  indexOf() 函数是最常用的存取函数之一,用来查找传进来的参数在目标数组中是否存在。 如果目标数组包含该参数,就返回该元素在数组中的索引;如果不包含,就返回 -1。

  如果数组中包含多个相同的元素,indexOf() 函数总是返回第一个与参数相同的元素的索 引。有另外一个功能与之类似的函数:lastIndexOf(),该函数返回相同元素中最后一个元 素的索         引,如果没找到相同元素,则返回 -1。

6.数组的字符串表示

  有两个方法可以将数组转化为字符串:join() 和 toString()。这两个方法都返回一个包含 数组所有元素的字符串,各元素之间用逗号分隔开。

7.由已有数组创建新数组

  concat() 和 splice() 方法允许通过已有数组创建新数组。concat 方法可以合并多个数组 创建一个新数组,splice() 方法截取一个数组的子集创建一个新数组。

  我们先来看看 concat() 方法的工作原理。该方法的发起者是一个数组,参数是另一个数 组。作为参数的数组,其中的所有元素都被连接到调用 concat() 方法的数组后面。下面的 程序展         示了 concat() 方法的工作原理:

  var cis= ["zhangsan", "lisi"];

  var dmp = ["wangwu","zhaoliu"];

  var itDiv = cis.concat(dmp);

  console.log(itDiv);

  itDiv = dmp.concat(cis);

  console.log(itDiv);

  输出为:

     zhangsan,lisi,wangwu,zhaoliu

     wangwu,zhaoliu,zhangsan,lisi

  第一行首先输出 cis 数组里的元素,第二行首先输出 dmp 数组里的元素。

 

  splice() 方法从现有数组里截取一个新数组。该方法的第一个参数是截取的起始索引,第 二个参数是截取的长度。下面的程序展示了 splice() 方法的工作原理:

  var itDiv = ["Mike","Clayton","Terrill","Raymond","Cynthia","Danny","Jennifer"];

  var dmpDept = itDiv.splice(3,3);

  var cisDept = itDiv;

     console.log(dmpDept); // Raymond,Cynthia,Danny

  console.log(cisDept); // Mike,Clayton,Terrill,Jennifer

  splice() 方法还有其他用法,比如为一个数组增加或移除元素

8.可变函数

  JavaScript 拥有一组可变函数,使用它们,可以不必引用数组中的某个元素,就能改变数组 内容。这些函数常常化繁为简,让困难的事情变得容易,就像下面我们将要看到的那样。

  8.1为数组添加元素

    有两个方法可以为数组添加元素:push() 和 unshift()。push() 方法会将一个元素添加到 数组末尾:

    var nums = [1,2,3,4,5];

    console.log(nums); // 1,2,3,4,5

    nums.push(6);

    console.log(nums); // 1,2,3,4,5,6

 

    unshift() 方法可以将元素添加在数组的开头,下述代码展示了该方法的用法:

    var nums = [2,3,4,5];

    console.log(nums); // 2,3,4,5

    var newnum = 1;

    nums.unshift(newnum);

    console.log(nums); // 1,2,3,4,5

    nums = [3,4,5];

       nums.unshift(newnum,1,2);

    console.log(nums); // 1,2,3,4,5

    第二次出现的 unshift() 方法展示了可以通过一次调用,为数组添加多个元素

  8.2从数组中删除元素

    使用 pop() 方法可以删除数组末尾的元素

    var nums = [1,2,3,4,5,9];

    nums.pop();

       console.log(nums); // 1,2,3,4,5

    shift() 方法可以删除数组的第一个元素,下述代码展示了该方法的用法:

    var nums = [9,1,2,3,4,5];

    nums.shift();

    console.log(nums); // 1,2,3,4,5

    

    pop() 和 shift() 方法都将删掉的元素作为方法的 返回值返回,因此可以使用一个变量来保存删除的元素:

    var nums = [6,1,2,3,4,5];

    var first = nums.shift(); // first gets the value 6

    nums.push(first);

    console.log(nums); // 1,2,3,4,5,6

  8.3从数组中间位置添加和删除元素

    删除数组中的第一个元素和在数组开头添加一个元素存在同样的问题——两种操作都需要将 数组中的剩余元素向前或向后移,然而 splice() 方法可以帮助我们执行其中任何一                     种操作。

    使用 splice() 方法为数组添加元素,需提供如下参数:

      1.起始索引(也就是你希望开始添加元素的地方);

       2.需要删除的元素个数(添加元素时该参数设为 0);

       3. 想要添加进数组的元素。

    看一个简单的例子。下面的程序在数组中间插入元素:

    var nums = [1,2,3,7,8,9];

    var newElements = [4,5,6];

    nums.splice(3,0,newElements);

    console.log(nums); // 1,2,3,4,5,6,7,8,9

    要插入数组的元素不必组织成一个数组,它可以是任意的元素序列,比如:

    var nums = [1,2,3,7,8,9];

    nums.splice(3,0,4,5,6);

    console.log(nums);  // 1,2,3,4,5,6,7,8,9

    下面是使用 splice() 方法从数组中删除元素的例子:

      var nums = [1,2,3,100,200,300,400,4,5];

      nums.splice(3,4);

      console.log(nums); // 1,2,3,4,5

  8.4为数组排序

    剩下的两个可变方法是为数组排序。第一个方法是 reverse(),该方法将数组中元素的顺 序进行翻转。下面这个例子展示了该如何使用该方法:

    var nums = [1,2,3,4,5];

     nums.reverse();

     console.log(nums); // 5,4,3,2,1

    对数组进行排序是经常会遇到的需求,如果元素是字符串类型,那么数组的可变方法 sort() 就非常好使:

    var names = ["David","Mike","Cynthia","Clayton","Bryan","Raymond"];

    names.sort();

    console.log(names); // Bryan,Clayton,Cynthia,David,Mike,Raymond

    但是如果数组元素是数字类型,sort() 方法的排序结果就不能让人满意了:

    var nums = [3,1,2,100,4,200];

    nums.sort();

    console.log(nums); // 1,100,2,200,3,4

    sort() 方法是按照字典顺序对元素进行排序的,因此它假定元素都是字符串类型,在上一 个例子中,即使元素是数字类型,也被认为是字符串类型。为了让 sort() 方法也能排序数 字                类型的元素,可以在调用方法时传入一个大小比较函数,排序时,sort() 方法将会根据 该函数比较数组中两个元素的大小,从而决定整个数组的顺序。

    对于数字类型,该函数可以是一个简单的相减操作,从一个数字中减去另外一个数字。如 果结果为负,那么被减数小于减数;如果结果为 0,那么被减数与减数相等;如果结果为                   正,那么被减数大于减数。

 

    将这些搞清楚之后,传入一个大小比较函数,再来看看前面的例子:

    function compare(num1, num2) {

      return num1 - num2;

    }

    var nums = [3,1,2,100,4,200];

    nums.sort(compare);

    console.log(nums); // 1,2,3,4,100,200

    sort() 函数使用了 compare() 函数对数组按照数字大小进行排序,而不是按照字典顺序。

9.迭代器方法

  这些方法对数组中的每个元素应用一个函数,可以返回一个 值、一组值或者一个新数组。

  9.1不生成新数组的迭代器方法

    我们要讨论的第一组迭代器方法不产生任何新数组,相反,它们要么对于数组中的每个元 素执行某种操作,要么返回一个值。

    这组中的第一个方法是 forEach(),该方法接受一个函数作为参数,对数组中的每个元素 使用该函数。下面这个例子展示了如何使用该方法:

    function square(num) {

      console.log(num, num * num);

    }

    var nums = [1,2,3,4,5,6,7,8,9,10];

    nums.forEach(square);

    该程序的输出为:

      1 1

      2 4

      3 9

      4 16

      5 25

      6 36

      7 49

      8 64

      9 81

      10 100

    另一个迭代器方法是 every(),该方法接受一个返回值为布尔类型的函数,对数组中的每 个元素使用该函数。如果对于所有的元素,该函数均返回 true,则该方法返回 true。下面 是一个例子:

    function isEven(num) {

      return num % 2 == 0;

    }

    var nums = [2,4,6,8,10];

    var even = nums.every(isEven);

    if (even) {

      console.log("all numbers are even");

    } else {

      console.log("not all numbers are even");

     }

    输出为: all numbers are even

    将数组改为: var nums = [2,4,6,7,8,10];

    输出为: not all numbers are even

    some() 方法也接受一个返回值为布尔类型的函数,只要有一个元素使得该函数返回 true, 该方法就返回 true。比如:

    function isEven(num) {

      return num % 2 == 0;

    }

    var nums = [1,2,3,4,5,6,7,8,9,10];

    var someEven = nums.some(isEven);

    if (someEven) {

      console.log("some numbers are even");

     } else {

      console.log("no numbers are even");

    }

    nums = [1,3,5,7,9];

    someEven = nums.some(isEven);

    if (someEven) {

      console.log("some numbers are even");

    } else {

      console.log("no numbers are even");

    }

    该程序的输出为:

    some numbers are even

    no numbers are even

    reduce() 方法接受一个函数,返回一个值。该方法会从一个累加值开始,不断对累加值和 数组中的后续元素调用该函数,直到数组中的最后一个元素,最后返回得到的累加值。下 面这个例子展示了如何使用 reduce() 方法为数组中的元素求和:

    function add(runningTotal, currentValue) {

      return runningTotal + currentValue;

    }

    var nums = [1,2,3,4,5,6,7,8,9,10];

    var sum = nums.reduce(add);

    console.log(sum); // 显示 55

 

    JavaScript 还提供了 reduceRight() 方法,和 reduce() 方法不同,它是从右到左执行。

  9.2生成新数组的迭代器方法

    有两个迭代器方法可以产生新数组:map() 和 filter()。map() 和 forEach() 有点儿像,对 数组中的每个元素使用某个函数。两者的区别是 map() 返回一个新的数组,该数组的元素 是对原有元素应用某个函数得到的结果。下面给出一个例子:

    function curve(grade) {

      return grade += 5;

    }

    var grades = [77, 65, 81, 92, 83];

    var newgrades = grades.map(curve);

    console.log(newgrades); // 82, 70, 86, 97, 88

 

    filter() 和 every() 类似,传入一个返回值为布尔类型的函数。和 every() 方法不同的是, 当对数组中的所有元素应用该函数,结果均为 true 时,该方法并不返回 true,而是返回 一个新数组,该数组包含应用该函数后结果为 true 的元素。下面是一个例子:

    function isEven(num) {

      return num % 2 == 0;

    }

    function isOdd(num) {

      return num % 2 != 0;

    }

    var nums = [];

    for (var i = 0; i < 20; ++i) {

      nums[i] = i+1;

    }

    var evens = nums.filter(isEven);

    console.log(evens);  //2,4,6,8,10,12,14,16,18,20

    var odds = nums.filter(isOdd);

    console.log(odds);  //1,3,5,7,9,11,13,15,17,19

总结

  这个只是我的个人总结,有哪里不对和需要改进的地方,请给我留言,大家一起成长。感谢次平台