前端——数据结构与算法之数组与链表

256 阅读5分钟

什么是数据结构与算法?

  1. 数据结构

存储和运算是程序的两大基础功能,数据结构是专门研究数据存储的学科。

很多时候,我们无法仅使用简单的数字、字符串、布尔就能完整的描述数据,可能我们希望使用数组、对象、或它们组合而成的复合结构来对数据进行描述。这种复合的结构就是数据结构。

而在实际开发中,我们会发现很多场景中使用的数据结构有着相似的特征,于是,数据结构这门学科,就把这些相似的结构单独提取出来进行研究。

在这门学科中,常见的数据结构有:数组、链表、树、图等

  1. 什么是算法

存储和运算是程序的两大基础功能,算法是专门研究运算过程的学科。

一个程序,很多时候都需要根据一种已知数据,通过计算,得到另一个未知数据,这个运算过程使用的方法,就是算法。

而在很多的场景中,它们使用的算法有一些共通的特点,于是把这些共通的算法抽象出来,就行了常见算法。

从一个更高的角度来对算法划分,常见的算法有:穷举法、分治法、贪心算法、动态规划

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

一个面向的是存储,一个面向的是运算,它们共同构成了计算机程序的两个重要部分。

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

线性结构

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

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

数组

数字是一整块连续的内存空间,它由固定数量的元素组成,数组有如下特征

  1. 整个数组占用的内存空间是连续的。
  2. 数组中元素的数量是固定的(不可增加也不可减少),创建数组时必须要指定其长度
  3. 每个元素占用的内存大小是完全一样的

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

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

JS的数组其实不是真正的数组,实际底层是链表

链表

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

  1. 每个元素除了存储数据,需要额外的内存存储一个引用(地址),来指向下一个元素
  2. 每个元素占用的空间不是连续的
  3. 往往需要使用链表的第一个节点(根节点)来代表整个链表
    根据链表的基本特征,我们可以推导出它具有以下特点:
  4. 长度是可变的,随时可以增加和删除元素
  5. 插入和删除元素效率高
  6. 由于要存储地址,会增加额外的内存开销
  7. 通过下标查询链表中的某个节点,效率很低,因此链表的下标遍历低

下面我会用JavaScript来手写链表和一些相关函数

  1. 先来手写链表
function Node(value){
  this.value = value;
  this.next = null;
}
var a = new Node("a");
var b = new Node("b");
var c = new Node("c");
var d = new Node("d");
a.next = b;
b.next = c;
c.next = d;

现在的结构是这样的

需要说明的是,链表的每一个都可以当作根节点

这就是一个链表了,下面来写一些相关函数

  1. 遍历打印
  2. 获取链表的长度
  3. 通过下标获取链表中的某个数据
  4. 通过下标设置链表中的某个数据
  5. 在链表某一个节点之后加入一个新节点
  6. 在链表末尾加入一个新节点
  7. 删除一个链表节点
  8. 链表倒序
1.遍历打印
function print(root){
  //分治法
  if(root){
    console.log(root.value); //打印自己
    print(root.next);
  }
}
print(a); 把a给它,输出 a b c d
2.获取链表的长度
function count(root){
  if(!root) return 0;
  return 1 + count(root.next);
}
var len = count(a); 输出4 
3.通过下标获取链表中的某个数据
function getNode(root,index){
 /**
     * 判断某个节点是否是我要查找的节点
     * @param {*} node 表示某个节点
     * @param {*} i 该节点是第几个节点
 */
  function _getNode(node,i){
    if(!node) return null;
    if(i === index) return node.value;
    return _getNode(node.next,++i);
  }
  return _getNode(root,0);
}
var nd = getNode(a,2);  
console.log(nd);// c
4.通过下标设置链表中的某个数据
function setNode(root,index,newValue){
  function  _setNode(node,i){
    if(!node) return null;
    if(i === index) node.value = newValue;
    _setNode(node.next,++i);
  }
  return  _setNode(root,0);
}
setNode(a,2,"f"); //此时c变成f了
5. 在链表某一个节点之后加入一个新节点
function insertAfter(node,newValue){
  var newNode = new Node(newValue); //构建新的节点
  newNode.next = node.next;
  node.next = newNode;
}
insertAfter(c,"e"); 在c后面插入e
6.在链表末尾加入一个新节点
function push(root,newValue){
  if(!root.next){ //如果下一个节点没有了,那这个就是最后一个节点
    var newNode = new Node(newValue);
    root.next = newNode;
  }else{
    push(root.next,newValue);  //自己不是最后一个,看下一个
  }
}
push(a,"e");
7. 删除一个链表节点
function removeNode(root,removeValue){
  if(!root || !root.next) return;
  if(root.next.value === removeValue){
    root.next = root.next.next;
  }else{
    removeNode(root.next,removeValue);
  }
}
removeNode(a,"d");
8. 链表倒序
function reverse(root){
  if(!root || !root.next ) return root;
  if(!root.next.next){ //只有2个节点的情况
    var temp = root.next;
    root.next.next = root;
    root.next = null;
    return temp;
  }else{ //2个节点的情况
    var temp = reverse(root.next);
    root.next.next = root;
    root.next = null;
    return temp;
  }
}
var temp = reverse(a); //反转后的结果

这下8个练习都做完了。下一章是查找和排序