常见问题

  1. 什么是数据结构?

    用于表示数据关系的结构

    常见的数据结构有:数组、链表、树、图等

  2. 什么是算法?

    根据已知数据得到结果数据的计算方法

    常见的算法有:穷举法、分治法、贪心算法、动态规划

  3. 数据结构和算法有什么关系?

    数据结构关心的是如何使用合适的结构存储数据

    算法关心的是计算过程

    有了相应的数据结构,免不了对这种数据结构的各种变化进行运算,所以,很多时候,某种数据结构都会自然而然的搭配不少算法。

  4. 数据结构和算法课程使用什么计算机语言?

    数据结构和算法属于计算机基础课程,它们和具体的语言无关,用任何语言都可以实现。

    本课程采用JavaScript语言。

递归知识回顾

递归(recursion)是函数式编程思想的产物,它使用数学函数的思想进行运算,只要在数学逻辑上是合理的,即代码中的函数一定合理

使用递归时,无须深究其运行过程!

斐波拉契数列的特点是:第1位和第2位固定为1,后面的位,其数字等于前两位之和,比如:

[1, 1, 2, 3, 5, 8, 13, 21, ……]

求斐波拉契数列第n位的值,n>0

如果使用函数f(n)来表示斐波拉契数列第n位的值,通过数学分析,可以轻松得到:

1
f(1) = 1
2
f(2) = 1
3
f(n) = f(n-1) + f(n-2)

以上等式考虑到了所有情况,并且在数学逻辑上是合理的,因此,可以轻松书写到代码中:

1
// 求斐波拉契数列第n位的值
2
function f(n){
3
    if(n === 1 || n === 2){
4
      	return 1;
5
    }
6
    return f(n-1) + f(n-2);
7
}

线性结构

线性结构是数据结构中的一种分类,用于表示一系列的元素形成的有序集合。

常见的线性结构包括:数组、链表、栈、队列

数组

特别注意:这里所说的数组是数据结构中的数组,和JS中的数组不一样

数组是一整块连续的内存空间,它由固定数量的元素组成,数组具有以下基本特征:

  1. 整个数组占用的内存空间是连续的

  2. 数组中元素的数量是固定的(不可增加也不可减少),创建数组时就必须指定其长度

  3. 每个元素占用的内存大小是完全一样的
    image.png

根据数组的基本特征,我们可以推导出数组具有以下特点

  1. 通过下标寻找对应的元素效率极高,因此遍历速度快
  2. 无法添加和删除数据,虽然可以通过某种算法完成类似操作,但会增加额外的内存开销或时间开销
  3. 如果数组需要的空间很大,可能一时无法找到足够大的连续内存

JS中的数组

在ES6之前,JS没有真正意义的数组,所谓的Array,实际上是一个对象。

ES6之后,出现真正的数组(类型化数组),但是由于只能存储数字,因此功能有限

目前来讲,JS语言只具备不完善的数组(类型化数组)

链表

为弥补数组的缺陷而出现的一种数据结构,它具有以下基本特征

  1. 每个元素除了存储数据,需要有额外的内存存储一个引用(地址),来指向下一个元素

  2. 每个元素占用的内存空间并不要求是连续的

  3. 往往使用链表的第一个节点(根节点)来代表整个链表

    image-20200724152732227

根据链表的基本特征,我们可以推导出它具有以下特点

  1. 长度是可变的,随时可以增加和删除元素
  2. 插入和删除元素的效率极高
  3. 由于要存储下一个元素的地址,会增加额外的内存开销
  4. 通过下标查询链表中的某个节点,效率很低,因此链表的下标遍历效率低

手动用代码实现链表

实际上,很多语言本身已经实现了链表,但链表作为一种基础的数据结构,通过手写代码实现链表,不仅可以锻炼程序思维和代码转换能力,对于后序的复杂数据结构的学习也是非常有帮助的。

因此,手写链表是学习数据结构和算法的一门基本功

手写一个链表结构,并完成一些链表的相关函数,要实现以下功能:

创建一个构造函数,用来创建Node节点

1
/**
2
 * 创建node节点
3
 * @param {*} value 要创建node节点的值
4
 */
5
function Node(value) {
6
  this.value = value;
7
  this.next = null;
8
}
9
var a = new Node("A");
10
var b = new Node("B");
11
var c = new Node("C");
12
var d = new Node("D");
13
var e = new Node("E");
14
a.next = b;
15
b.next = c;
16
c.next = d;
17
d.next = e;

注意: 一下所有的例子创建node节点都是以此构造函数生成,并创建一个链表。

  1. 遍历打印

    1
    /**
    2
     * 遍历打印整个链表的数据
    3
     * @param {*} node 
    4
     */
    5
    function print(node) {
    6
      if (!node) return;
    7
      console.log(node.value);
    8
      if (node.next) print(node.next);
    9
    }
    10
    print(a);

    打印出的值为ABCDE

    line-3.png

  2. 获取链表的长度

    1
    /**
    2
     * 获取链表的长度
    3
     * @param {} node
    4
     */
    5
    function getLength(node) {
    6
      if (!node) return 0;
    7
      return 1 + getLength(node.next);
    8
    }
    9
    10
    console.log(getLength(a));

    打印结果是5

  3. 通过下标获取链表中的某个数据

    1
    /**
    2
     * 通过下标  获取链表中的某个数据
    3
     * @param {*} node  链表
    4
     * @param {*} index  下标
    5
     */
    6
    function selectValue(node, index) {
    7
        /**
    8
         * 辅助函数 通过判断当前下标和要查找的下标是否相等来查找值
    9
         * @param {*} node  链表
    10
         * @param {*} index  要查找的值的索引
    11
         * @param {*} curIndex  当前的索引值
    12
         */
    13
      function _selectValue(node, index, curIndex) {
    14
        if (!node) throw Error("超出界限");
    15
        if (index === curIndex) return node.value;
    16
        curIndex++;
    17
        return _selectValue(node.next, index, curIndex);
    18
      }
    19
      return _selectValue(node, index, 0);
    20
    }
    21
    22
    console.log(selectValue(a, 2));

    输出结果是C

  4. 通过下标设置链表中的某个数据

    1
    /**
    2
     * 通过下标设置(修改)链表中的某个数据
    3
     * @param {*} node  链表
    4
     * @param {*} index  要修改的索引
    5
     * @param {*} value  要修改为的值
    6
     */
    7
    function setNode(node, index, value) {
    8
      var nodes = node;
    9
      /**
    10
       * 辅助函数  通过判断当前下标和要查找的下标是否相等来修改值
    11
       * @param {*} node 当前的链表
    12
       * @param {*} index 要修改的索引
    13
       * @param {*} curIndex 当前的索引值
    14
       */
    15
      function _setNode(node, index, curIndex) {
    16
        if (!node) throw Error("数据错误");
    17
        if (index === curIndex) {
    18
          node.value = value;
    19
          return nodes;
    20
        }
    21
        curIndex++;
    22
        return _setNode(node.next, index, curIndex);
    23
      }
    24
      return _setNode(node, index, 0);
    25
    }
    26
    27
    console.log(setNode(a, 3, "k"));Node {value: "A", next: Node}
    28
    29
    // 打印的数据
    30
    next: Node
    31
    next: Node
    32
    next: Node
    33
    next: Node
    34
    next: null
    35
    value: "E"
    36
    __proto__: Object
    37
    value: "k"
    38
    __proto__: Object
    39
    value: "C"
    40
    __proto__: Object
    41
    value: "B"
    42
    __proto__: Object
    43
    value: "A"
  5. 在链表末尾加入一个新节点

    1
    /**
    2
     * 在链表末尾加入一个新节点
    3
     * @param {*} node  链表
    4
     * @param {*} pushValue  要加入的新值
    5
     */
    6
    function pushNode(node, pushValue) {
    7
      if (node.next) {
    8
        return pushNode(node.next, pushValue);
    9
      } else {
    10
        var w = new Node(pushValue);
    11
        return (node.next = w);
    12
      }
    13
    }
    14
    console.log(pushNode(a, "w"));
  6. 在链表某一个节点之后加入一个新节点

    1
    2
    /**
    3
     * 在链表的某一个节点以后加入一个新的节点
    4
     * @param {*} node 
    5
     * @param {*} beforeNode 
    6
     * @param {*} value 
    7
     */
    8
    function insertNode(node, beforeNode, value) {
    9
      if (node === beforeNode) {
    10
        var newNode = new Node(value);
    11
        newNode.next = node.next;
    12
        node.next = newNode;
    13
      } else {
    14
        return insertNode(node.next, beforeNode, value);
    15
      }
    16
    }
    17
    18
    insertNode(a, b, "l")
    19
    console.log(a);
  7. 删除一个链表节点

1
/**
2
 * 删除一个链表的某一个节点
3
 * @param {*} node 
4
 * @param {*} deleteValue 
5
 */
6
function deleteNode(node, deleteValue) {
7
  if (!node) throw Error("数据错误");
8
  if (node.next === deleteValue) {
9
    node.next = deleteValue.next;
10
  } else {
11
    deleteNode(node.next, deleteValue);
12
  }
13
}
14
15
deleteNode(a, c);
16
print(a)//A B l k E w

完整代码

1
/**
2
 * 创建node节点
3
 * @param {*} value 要创建node节点的值
4
 */
5
function Node(value) {
6
  this.value = value;
7
  this.next = null;
8
}
9
10
var a = new Node("A");
11
var b = new Node("B");
12
var c = new Node("C");
13
var d = new Node("D");
14
var e = new Node("E");
15
16
a.next = b;
17
b.next = c;
18
c.next = d;
19
d.next = e;
20
console.log(a);
21
22
/**
23
 * 遍历打印整个链表的数据
24
 * @param {*} node
25
 */
26
function print(node) {
27
  if (!node) return;
28
  console.log(node.value);
29
  if (node.next) print(node.next);
30
}
31
print(a);
32
33
/**
34
 * 获取链表的长度
35
 * @param {} node
36
 */
37
function getLength(node) {
38
  if (!node) return 0;
39
  return 1 + getLength(node.next);
40
}
41
42
console.log(getLength(a));
43
44
/**
45
 * 通过下标  获取链表中的某个数据
46
 * @param {*} node  链表
47
 * @param {*} index  下标
48
 */
49
function selectValue(node, index) {
50
  /**
51
   * 辅助函数 通过判断当前下标和要查找的下标是否相等来查找值
52
   * @param {*} node  链表
53
   * @param {*} index  要查找的值的索引
54
   * @param {*} curIndex  当前的索引值
55
   */
56
  function _selectValue(node, index, curIndex) {
57
    if (!node) {
58
      throw Error("超出界限");
59
    }
60
    if (index === curIndex) {
61
      return node.value;
62
    }
63
    curIndex++;
64
    return _selectValue(node.next, index, curIndex);
65
  }
66
  return _selectValue(node, index, 0);
67
}
68
69
console.log(selectValue(a, 2));
70
71
/**
72
 * 通过下标设置(修改)链表中的某个数据
73
 * @param {*} node  链表
74
 * @param {*} index  要修改的索引
75
 * @param {*} value  要修改为的值
76
 */
77
function setNode(node, index, value) {
78
  var nodes = node;
79
  /**
80
   * 辅助函数  通过判断当前下标和要查找的下标是否相等来修改值
81
   * @param {*} node 当前的链表
82
   * @param {*} index 要修改的索引
83
   * @param {*} curIndex 当前的索引值
84
   */
85
  function _setNode(node, index, curIndex) {
86
    if (!node) throw Error("数据错误");
87
    if (index === curIndex) {
88
      node.value = value;
89
      return nodes;
90
    }
91
    curIndex++;
92
    return _setNode(node.next, index, curIndex);
93
  }
94
  return _setNode(node, index, 0);
95
}
96
97
console.log(setNode(a, 3, "k"));
98
99
/**
100
 * 在链表末尾加入一个新节点
101
 * @param {*} node  链表
102
 * @param {*} pushValue  要加入的新值
103
 */
104
function pushNode(node, pushValue) {
105
  if (node.next) {
106
    return pushNode(node.next, pushValue);
107
  } else {
108
    var w = new Node(pushValue);
109
    return (node.next = w);
110
  }
111
}
112
console.log(pushNode(a, "w"));
113
114
/**
115
 * 在链表的某一个节点以后加入一个新的节点
116
 * @param {*} node
117
 * @param {*} beforeNode
118
 * @param {*} value
119
 */
120
function insertNode(node, beforeNode, value) {
121
  if (node === beforeNode) {
122
    var newNode = new Node(value);
123
    newNode.next = node.next;
124
    node.next = newNode;
125
  } else {
126
    return insertNode(node.next, beforeNode, value);
127
  }
128
}
129
130
insertNode(a, b, "l");
131
console.log(a);
132
133
/**
134
 * 删除一个链表的某一个节点
135
 * @param {*} node 
136
 * @param {*} deleteValue 
137
 */
138
function deleteNode(node, deleteValue) {
139
  if (!node) throw Error("数据错误");
140
  if (node.next === deleteValue) {
141
    node.next = deleteValue.next;
142
  } else {
143
    deleteNode(node.next, deleteValue);
144
  }
145
}
146
147
deleteNode(a, c);
148
print(a)

注意:部分文章可能会在不就的将来更新

如果能够帮助到你,是小编最大的荣幸

当然 有 不好的地方 请大家帮忙指出 学习永无止境

小编一直认为 人外有人 天外有天 一起学习 共同进步

让我们共同加油吧!!!