数据结构和算法,和语言是无关的

单向链表

  • 既然有数组为什么用链表
    • 数组的创建需要申请一段连续的内存空间(一整块的内存),并且大小是固定的(大多数编程语言数组都是固定的),所以如果当前数组不能满足容量需求时,需要扩容。(一般情况是申请一个更大的数组,比如2倍,然后将原数组中的元素复制过去)
    • 而且在数组开头或中间位置插入数据的成本很高,需要进行大量元素位移
    • 为了解决上面的问题,我们需要链表这种数据结构
  • 特点
    • 不同于数组,链表中的元素在内存中不必是连续的空间
    • 链表的每个元素由一个存储元素本身的节点,和一个指向下一个元素的引用(有些语言称为指针)组成
  • 相较于数组的优点
    • 内存空间不必是连续的,可以充分利用计算机的内存。实现灵活的动态管理
    • 链表不必在创建时就确定大小,并且大小可以无限的延伸下去
    • 链表在插入和删除数据时,时间复杂度可以达到O(1),相对数组效率高很多
  • 相较于数组的缺点
    • 链表访问任何一个位置的元素,都需要从头开始访问。(无法跳过第一个元素访问任何其他元素)
    • 无法通过下标直接访问元素,需要从头一个个访问,直到找到对应元素
  • 单向链表封装
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    /**
    * 声明节点对象
    */
    function Node(element) {
    // 保存元素
    this.element = element;
    // 保存指针(指向下一个节点)
    this.next = null;
    }
    /**
    * 声明链表
    */
    function LinkedList() {
    // 链表头
    this.head = null;
    // 链表长度
    this.length = 0;
    // append(element) 向链表尾部追加元素
    this.append = function(element) {
    // 1.根据element创建Node对象
    var newNode = new Node(element);
    // 2.追加到最后
    if (!this.head) {
    // 节点不存在的时候
    this.head = newNode;
    } else {
    // 节点存在的时候,从Head依次找到最后一个节点
    var current = this.head;
    while(current.next) {
    current = current.next;
    }
    // 将最后一个节点的next指向新创建的Node就可以
    current.next = newNode;
    }
    // length
    this.length++;
    }
    // insert(position, element) 向链表的特定位置插入某一个元素
    this.insert = function (position, element) {
    // 1.判断越界
    if (position < 0 || position > this.length) {
    return false;
    }
    // 2. 创建新的节点
    var newNode = new Node(element);
    // 3. 插入元素
    if (position === 0) {
    // 插入到头部的时候
    newNode.next = this.head;
    this.head = newNode;
    } else {
    var index = 0;
    var current = this.head;
    var previous = null;
    while(index++ < position) {
    previous = current;
    current = current.next;
    }
    previous.next = newNode;
    newNode.next = current;
    }
    this.length++;
    return true;
    }
    // get(position) 获取对应位置的元素
    this.get = function (position) {
    // 1.判断越界
    if (position < 0 || position > this.length - 1) {
    return null;
    }
    // 2. 查找元素
    var index = 0;
    var current = this.head;
    while (index++ < position) {
    current = current.next;
    }
    return current.element;
    }
    // indexOf(element) 返回元素在列表中的索引。如果列表中没有该元素,则返回-1
    this.indexOf = function (element) {
    // 1. 获取第一个元素
    var current = this.head;
    var index = 0;
    while(current) {
    if (current.element === element) {
    return index;
    }
    current = current.next;
    index++;
    }
    return -1;
    }
    // update(position, element) 修改某个位置的元素
    this.update = function (position, element) {
    // 1.remove
    var result = this.removeAt(position);
    // 2.insert
    this.insert(position, element);
    return result;
    }
    // removeAt(position) 从列表指定位置移除一项
    this.removeAt = function (position) {
    // 1.判断越界
    if (position < 0 || position > this.length - 1) {
    return null;
    }
    // 2.删除元素
    var current = this.head;
    var previous = null;
    var index = 0;
    if (position === 0) {
    this.head = current.next;
    } else {
    while (index++ < position) {
    previous = current;
    current = current.next;
    }
    // 到这里说明已经找到要删除的元素
    previous.next = current.next;
    }
    this.length--;
    // 返回删除的元素
    return current.element;
    }
    // remove(element) 从列表中移除一项
    this.remove = function (element) {
    // 1.获取删除元素的位置
    var index = this.indexOf(element);
    if (index === -1) return;
    // 2.删除元素
    var result = this.removeAt(index);
    return result
    }
    // isEmpty() 链表不包含任何元素,返回true
    this.isEmpty = function () {
    return this.length === 0;
    }
    // size() 链表中元素个数
    this.isEmpty = function () {
    return this.length;
    }
    }

双向链表

数据结构和算法,和语言是无关的

  • 队列的特点

    1. 一种受限的线性表,先进先出(FIFO : first in first out)
    2. 其限制是仅允许在表的前端进行删除
    3. 而在表的后端(rear)进行插入操作
  • 生活中类似的队列结构

    • 电影院,商场甚至是厕所排队
    • 优先排队的人优先处理(买票,结账等)
  • 打印队列

    • 有5份文档需要打印,这些文档会按次序放入到打印队列
    • 打印机会依次从队列中取出文档,优先放入的文档,优先被取出打印
    • 直到队列中不再有新的文档
  • 线程队列

    • 在开发中,为了让任务可以并行处理,通常会开启多个线程
    • 但是,我们不能让大量的线程同时运行处理任务(占用过多资源)
    • 这个时候,如果有需要开启线程处理任务的情况,我们就会使用线程队列
    • 线程队列会依照次序来启动线程,并且处理对应的任务
  • 队列的实现(基于数组)

    • 队列的常见操作

      • enqueue(element) : 向队列尾部添加一个(或多个)新的项
      • dequeue() : 移除队列第一(即排在队列最前面的)项,并返回被移除的元素
      • front() : 返回队列中的第一个元素,最先被添加,也将是最先被移除的元素。但是队列不做任何变动(不移除元素,只返回元素信息。类似栈中peek)
      • isEmpty() : 如果队列里没有任何元素就返回true,否则返回false
      • size() : 返回队列里元素个数
    • 队列的实现

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
       // 声明队列
      function Queue() {
      // 栈中属性
      this.items = [];
      // enqueue 添加元素到队列尾部
      this.enqueue = function(element) {
      this.items.push(element);
      }
      // dequeue 移除队列第一项
      this.dequeue = function() {
      return this.items.shift();
      }
      // front 返回队列中的第一个元素
      this.front = function() {
      if (this.isEmpty()) return null;
      return this.items[0];
      }
      // isEmpty 队列没有任何元素就返回true,否则返回false
      this.isEmpty = function() {
      return this.items.length === 0;
      }
      // size 返回队列里元素个数
      this.size = function() {
      return this.items.length;
      }
      }
  • 面试题-击鼓传花

    • 要求

      1. 几个朋友一起玩个游戏,围在一圈,开始数数,数到某个数字的人自动淘汰

      2. 最后剩下的这个人会获胜,请问最后剩下的是哪个人

    • 实现

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      var nameList = ["Why", "Tom", "LiLei", "Lucy"];
      transferGame(nameList, 3);
      /**
      * 依次从队列头取出某个人,没数到某个数字的人,再放回到队列尾部
      * 例: Why Tom LiLei Lucy 数到3的人淘汰,最后剩谁
      * @param nameList 参加的所有人
      * @param number 数到几被淘汰
      */
      function transferGame(nameList, number) {
      var queue1 = new Queue();
      // 初始化队列
      for (let name of nameList) {
      queue1.enqueue(name);
      }
      // 方法1
      var count = 1;
      while(queue1.size() > 1) {
      var name = queue1.dequeue();
      if (count === number) {
      // 淘汰的人的下一个人
      name = queue1.dequeue();
      count = 1;
      }
      count ++;
      queue1.enqueue(name);
      }
      console.log("方法1 : " + queue1.items);
      // 方法2
      var queue2 = new Queue();
      for (let name of nameList) {
      queue2.enqueue(name);
      }
      while(queue2.size() > 1) {
      for (let i = 1; i < 3; i++) {
      // 没数到3的时候,队列里出来的人依次再添加回去
      queue2.enqueue(queue2.dequeue());
      }
      queue2.dequeue();
      }
      console.log("方法2 : " + queue2.items);
      }
  • 优先级队列

    • 普通队列插入一个元素,数据会被放到后端

    • 优先级队列在插入一个元素的时候,会考虑该数据的优先级

    • 和其他数据的优先级进行比较

    • 比较完成后,得出这个元素在队列中的正确的位置

  • 优先级队列主要考虑的问题

    • 每个元素不再只是一个数据,而且包含数据的优先级
    • 在添加方式中,根据优先级放入正确的位置
  • 优先级队列的应用

    • 机场的登机顺序
      • 头等舱和商务舱乘客优先级高于经济舱乘客
      • 老年人和孕妇也享有高于其他乘客的优先级
    • 医院急诊科候诊室
      • 医生优先处理病情比较严重的患者
      • 一般情况下是按照排号的顺序
    • 计算机中也可以通过优先级队列来重新排序队列中任务的顺序
      • 每个线程处理的任务重要性不同, 可以通过优先级的大小, 来决定该线程在队列中被处理的次序
  • 优先级队列实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    function  PriorityQueue() {
    // 用父类的属性, 方法初始化子类
    // 相当于es6 extends
    Queue.call(this);
    // 重写方法
    this.enqueue = function (element, priority) {
    // 1.创建QueueElement对象
    var queueElement = new QueueElement(element, priority);
    // 2.考虑如何插入新的元素
    if (this.isEmpty()) {
    this.items.push(queueElement);
    } else {
    var added = false;
    for (var i = 0; i < this.items.length; i++) {
    if (this.items[i].priority > queueElement.priority) {
    // 0: 不删除index开始的元素
    this.items.splice(i, 0, queueElement);
    added = true;
    break;
    }
    }
    if (!added) {
    this.items.push(queueElement);
    }
    }
    }
    }
    var que = new PriorityQueue();
    que.enqueue("aa", 100);
    que.enqueue("bb", 150);
    que.enqueue("cc", 120);
    que.enqueue("dd", 90);
    console.log(que.items);
    // 如果原队列的原型上有某方法, 需要继承的话,通过以下方法
    function object(o) {
    function F() {};
    F.prototype = o;
    return new F();
    }
    function Inherit(subClass, parentClass) {
    // F实例 F实例的原型对象 = 父类的原型对象
    var temp = object(parentClass.prototype);
    // 为了让F实例作为子类的原型对象
    temp.constructor = subClass;
    subClass.prototype = temp;
    }
    Inherit(PriorityQueue, Queue);

数据结构和算法,和语言是无关的

  • 栈的特点

    1. 一种受限的线性表,后进先出(LIFO:last in first out)
    2. 其限制是仅允许在栈顶进行插入和删除运算
  • 函数调用栈

  • 栈结构面试
    有六个元素6,5,4,3,2,1的顺序进栈,问下列哪一个不是合法的出栈序列?()

    A. 5 4 3 6 1 2

    B. 4 5 3 2 1 6

    C. 3 4 6 5 2 1

    D. 2 3 4 1 5 6

  • 栈结构实现(基于数组)

    • 栈的常见操作

      • push(element) : 添加一个新元素到栈顶

      • pop() : 移除栈顶元素,同时返回被移除元素

      • peek() : 返回栈顶的元素,不对栈做任何修改(不会移除栈顶元素,仅仅返回它)

      • isEmpty() : 如果栈里没有任何元素就返回true,否则返回false

      • size() : 返回栈里元素个数

      • toString() : 将栈结构的内容以字符形式返回

    • 栈的实现

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      // 声明栈
      function Stack() {
      // 栈中属性
      this.items = [];
      // push 添加元素到栈顶
      this.push = function(element) {
      this.items.push(element);
      }
      // pop 移除栈顶元素, 同时返回移除的元素
      this.pop = function() {
      return this.items.pop();
      }
      // peek 返回栈顶元素
      this.peek = function() {
      if (this.isEmpty()) return null;
      return this.items[this.items.length - 1];
      }
      // isEmpty 栈里没有任何元素就返回true,否则返回false
      this.isEmpty = function() {
      return this.items.length === 0;
      }
      // size 返回栈里元素个数
      this.size = function() {
      return this.items.length;
      }
      // 将栈结构的内容以字符形式返回
      this.toString = function() {
      return this.items.toString();
      }
      }
    • 基于实现的栈通过小程序将10进制转为2进制

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      // 十进制转二进制
      function DecToBin(decNum) {
      var stack = new Stack();
      // 依次整除2, 余数即为2进制
      while(decNum > 0) {
      stack.push(decNum % 2);
      decNum = Math.floor(decNum / 2);
      }
      // 将栈中的2进制拼接(例如 10进制4 转换后 栈中为[0,0,1])
      var result = "";
      while(!stack.isEmpty()) {
      result += stack.pop();
      //debugger;
      }
      console.log(result);
      }

数据结构和算法,和语言是无关的

  • 数组

    • 数组是一种线性结构,可以在数组的任意位置插入和删除数据
    • 但是有时候为了实现某些机能,需要对这种任意性加以限制
    • 栈和队列就是比较常见的受限的线性结构
  • 底层数组特点

    1. 数组中必须存放相同类型数据
  1. 申请数组的内存空间时,必须指定数组大小
  • 影响数组性能的要素

    1. 数组扩容
    2. 元素位移
  • 数组扩容

    1. numA:16 ➡ 18
    2. 此时底层会申请一个新的,长度扩大1倍的数组 numB:32 []
    3. 将numA中的数据一个个拷贝到numB, 再依次添加17,18
    4. 将numA的内存释放(free)掉
    5. java中ArrayList默认长度是10, 超出10之后继续add的话,数组扩容到15(10 + 10 >> 1
    6. 参考自ArrayList源码private void grow(int minCapacity)
  • 元素位移

    1. 在数组最前面插入元素时,元素一个个向后移动
    2. 在中间或者前面插入或者删除元素性能非常低
  • 既然有以上的缺点,为什么还要用数组

    1. 因为在日常开发中, 数组存放到数组后,常用的是查找
    2. 由于有下标, 查找的速度非常快(O(1)的性能)
  • JavaScript数组底层实现

    1. 实际并不是数组,而是类似于hashMap

    2. 不同时期采用不同实现, 正靠近C的数组实现

    3. Diving deep into JavaScript array

  1. 认证一般如何设计和实现的?从性能方面考虑,不应该每次都访问DB
  1. 如果采用Token,Token里一般都存放什么样的信息
  1. mave管理项目常用命令
  1. java list去除重复元素
  1. Linux修改文件权限命令
  1. package.json的作用
  1. jvm编译 自定义一个java.lang.String可以编译并运行么
  1. java Lamdar表达式的好处