JavaScript中数据结构与算法(三):链表


Posted in Javascript onJune 19, 2015

我们可以看到在javascript概念中的队列与栈都是一种特殊的线性表的结构,也是一种比较简单的基于数组的顺序存储结构。由于javascript的解释器针对数组都做了直接的优化,不会存在在很多编程语言中数组固定长度的问题(当数组填满后再添加就比较困难了,包括添加删除,都是需要把数组中所有的元素全部都变换位置的,javascript的的数组确实直接给优化好了,如push,pop,shift,unshift,split方法等等…)

线性表的顺序存储结构,最大的缺点就是改变其中一个元素的排列时都会引起整个合集的变化,其原因就是在内存中的存储本来就是连贯没有间隙的,删除一个自然就要补上。针对这种结构的优化之后就出现了链式存储结构,换个思路,我们完全不关心数据的排列,我们只需要在每一个元素的内部把下一个的数据的位置给记录就可以了,所以用链接方式存储的线性表简称为链表,在链式结构中,数据=(信息+地址)

链式结构中,我们把地址也可以称为“链”,一个数据单元就是一个节点,那么可以说链表就是一组节点组成的合集。每一个节点都有一个数据块引用指向它的下一个节点

JavaScript中数据结构与算法(三):链表

数组元素是靠位置关系做逻辑引用,链表则是靠每一个数据元保存引用指针关系进行引用

这种结构上的优势就非常明显的,插入一个数据完全不需要关心其排列情况,只要把“链”的指向衔接上

这样做链表的思路就不会局限在数组上了,我们可以用对象了,只要对象之间存在引用关系即可

链表一般有,单链表、静态链表、循环链表、双向链表

单链表:就是很单一的向下传递,每一个节点只记录下一个节点的信息,就跟无间道中的梁朝伟一样做卧底都是通过中间人上线与下线联系,一旦中间人断了,那么就无法证明自己的身份了,所以片尾有一句话:"我是好人,谁知道呢?”

静态链表:就是用数组描述的链表。也就是数组中每一个下表都是一个“节”包含了数据与指向

循环链表:由于单链表的只会往后方传递,所以到达尾部的时候,要回溯到首部会非常麻烦,所以把尾部节的链与头连接起来形成循环

双向链表:针对单链表的优化,让每一个节都能知道前后是谁,所以除了后指针域还会存在一个前指针域,这样提高了查找的效率,不过带来了一些在设计上的复杂度,总体来说就是空间换时间了

综合下,其实链表就是线性表中针对顺序存储结构的一种优化手段,但是在javascript语言中由于数组的特殊性(自动更新引用位置),所以我们可以采用对象的方式做链表存储的结构

单链表

我们实现一个最简单的链表关系

function createLinkList() {
  var _this = {},
    prev = null;
  return {
    add: function(val) {
      //保存当前的引用
      prev = {
        data: val,
        next: prev || null
      }
    }
  }
}
var linksList = createLinkList(); 
linksList.add("arron1"); 
linksList.add("arron2"); 
linksList.add("arron3");
//node节的next链就是 -arron3-arron2-arron1

通过node对象的next去直引用下一个node对象,初步是实现了通过链表的引用,这种链式思路jQuery的异步deferred中的then方法,还有日本的cho45的jsderferre中都有用到。这个实现上还有一个最关键的问题,我们怎么动态插入数据到执行的节之后?

所以我们必须 要设计一个遍历的方法,用来搜索这个node链上的数据,然后找出这个对应的数据把新的节插入到当前的链中,并改写位置记录

//在链表中找到对应的节
var findNode = function createFindNode(currNode) {
  return function(key){
    //循环找到执行的节,如果没有返回本身
    while (currNode.data != key) {
      currNode = currNode.next;
    }
    return currNode;        
  }
}(headNode);

这就是一个查找当前节的一个方法,通过传递原始的头部headNode节去一直往下查找next,直到找到对应的节信息

这里是用curry方法实现的

那么插入节的的时候,针对链表地址的换算关系这是这样

a-b-c-d的链表中,如果要在c(c.next->d)后面插入一个f

a-b-c-f-d ,那么c,next->f , f.next-d

通过insert方法增加节

//创建节
function createNode(data) {
  this.data = data;
  this.next = null;
}
//初始化头部节
//从headNode开始形成一条链条
//通过next衔接
var headNode = new createNode("head");

//在链表中找到对应的节
var findNode = function createFindNode(currNode) {
  return function(key){
    //循环找到执行的节,如果没有返回本身
    while (currNode.data != key) {
      currNode = currNode.next;
    }
    return currNode;        
  }
}(headNode);

//插入一个新节
this.insert = function(data, key) {
  //创建一个新节
  var newNode = new createNode(data);
  //在链条中找到对应的数据节
  //然后把新加入的挂进去
  var current = findNode(key);
  //插入新的接,更改引用关系
  //1:a-b-c-d
  //2:a-b-n-c-d
  newNode.next = current.next;
  current.next = newNode;
};

首先分离出createNode节的构建,在初始化的时候先创建一个头部节对象用来做节开头的初始化对象

在insert增加节方法中,通过对headNode链的一个查找,找到对应的节,把新的节给加后之后,最后就是修改一下链的关系

如何从链表中删除一个节点?

由于链表的特殊性,我们a->b->c->d  ,如果要删除c那么就必须修改b.next->c为 b.next->d,所以找到前一个节修改其链表next地址,这个有点像dom操作中removeChild找到其父节点调用移除子节点

同样的我们也在remove方法的设计中,需要设计一个遍历往上回溯一个父节点即可

//找到前一个节
var findPrevious = function(currNode) {
  return function(key){
    while (!(currNode.next == null) &&
      (currNode.next.data != key)) {
      currNode = currNode.next;
    }
    return currNode;      
  }
}(headNode);

//插入方法
this.remove = function(key) {
  var prevNode = findPrevious(key);
  if (!(prevNode.next == null)) {
    //修改链表关系
    prevNode.next = prevNode.next.next;
  }
};

测试代码:

<!doctype html><button id="test1">插入多条数据</button>
<button id="test2">删除Russellville数据</button><div id="listshow"><br /></div><script type="text/javascript">
 //////////
 //创建链表 //
 //////////
 function createLinkList() {

 //创建节
 function createNode(data) {
  this.data = data;
  this.next = null;
 }

 //初始化头部节
 //从headNode开始形成一条链条
 //通过next衔接
 var headNode = new createNode("head");

 //在链表中找到对应的节
 var findNode = function createFindNode(currNode) {
  return function(key) {
  //循环找到执行的节,如果没有返回本身
  while (currNode.data != key) {
   currNode = currNode.next;
  }
  return currNode;
  }
 }(headNode);

 //找到前一个节
 var findPrevious = function(currNode) {
  return function(key) {
  while (!(currNode.next == null) &&
   (currNode.next.data != key)) {
   currNode = currNode.next;
  }
  return currNode;
  }
 }(headNode);


 //插入一个新节
 this.insert = function(data, key) {
  //创建一个新节
  var newNode = new createNode(data);
  //在链条中找到对应的数据节
  //然后把新加入的挂进去
  var current = findNode(key);
  //插入新的接,更改引用关系
  //1:a-b-c-d
  //2:a-b-n-c-d
  newNode.next = current.next;
  current.next = newNode;
 };

 //插入方法
 this.remove = function(key) {
  var prevNode = findPrevious(key);
  if (!(prevNode.next == null)) {
  //修改链表关系
  prevNode.next = prevNode.next.next;
  }
 };


 this.display = function(fn) {
  var currNode = headNode;
  while (!(currNode.next == null)) {
  currNode = currNode.next;
  fn(currNode.data)
  }
 };

 }


 var cities = new createLinkList();

 function create() {
 var text = '';
 cities.display(function(data) {
  text += '-' + data;
 });
 var div = document.createElement('div')
 div.innerHTML = text;
 document.getElementById("listshow").appendChild(div)
 }

 document.getElementById("test1").onclick = function() {
 cities.insert("Conway", "head");
 cities.insert("Russellville", "Conway");
 cities.insert("Carlisle", "Russellville");
 cities.insert("Alma", "Carlisle");
 create();
 }

 document.getElementById("test2").onclick = function() {
 cities.remove("Russellville");
 create()
 }

</script>

双链表

原理跟单链表是一样的,无非就是给每一个节增加一个前链表的指针

JavaScript中数据结构与算法(三):链表

增加节

//插入一个新节
this.insert = function(data, key) {
  //创建一个新节
  var newNode = new createNode(data);
  //在链条中找到对应的数据节
  //然后把新加入的挂进去
  var current = findNode(headNode,key);
  //插入新的接,更改引用关系
  newNode.next   = current.next;
  newNode.previous = current
  current.next   = newNode;
};

删除节

this.remove = function(key) {
  var currNode = findNode(headNode,key);
  if (!(currNode.next == null)) {
    currNode.previous.next = currNode.next;
    currNode.next.previous = currNode.previous;
    currNode.next     = null;
    currNode.previous   = null;
  }
};

在删除操作中有一个明显的优化:不需要找到父节了,因为双链表的双向引用所以效率比单链要高

测试代码:

<!doctype html><button id="test1">插入多条数据</button>
<button id="test2">删除Russellville数据</button><div id="listshow"><br /></div><script type="text/javascript">

 //////////
 //创建链表 //
 //////////
 function createLinkList() {

 //创建节
 function createNode(data) {
  this.data = data;
  this.next = null;
  this.previous = null
 }

   //初始化头部节
 //从headNode开始形成一条链条
 //通过next衔接
 var headNode = new createNode("head");

 //在链表中找到对应的数据
 var findNode = function(currNode, key) {
  //循环找到执行的节,如果没有返回本身
  while (currNode.data != key) {
  currNode = currNode.next;
  }
  return currNode;
 }

 //插入一个新节
 this.insert = function(data, key) {
  //创建一个新节
  var newNode = new createNode(data);
  //在链条中找到对应的数据节
  //然后把新加入的挂进去
  var current = findNode(headNode,key);
  //插入新的接,更改引用关系
  newNode.next   = current.next;
  newNode.previous = current
  current.next   = newNode;
 };

 this.remove = function(key) {
  var currNode = findNode(headNode,key);
  if (!(currNode.next == null)) {
  currNode.previous.next = currNode.next;
  currNode.next.previous = currNode.previous;
  currNode.next     = null;
  currNode.previous   = null;
  }
 };

 this.display = function(fn) {
  var currNode = headNode;
  while (!(currNode.next == null)) {
  currNode = currNode.next;
  fn(currNode.data)
  }
 };

 }


 var cities = new createLinkList();

 function create() {
 var text = '';
 cities.display(function(data) {
  text += '-' + data;
 });
 var div = document.createElement('div')
 div.innerHTML = text;
 document.getElementById("listshow").appendChild(div)
 }

 document.getElementById("test1").onclick = function() {
 cities.insert("Conway", "head");
 cities.insert("Russellville", "Conway");
 cities.insert("Carlisle", "Russellville");
 cities.insert("Alma", "Carlisle");
 create();
 }

 document.getElementById("test2").onclick = function() {
 cities.remove("Russellville");
 create()
 }


</script>

git代码下载:https://github.com/JsAaron/data_structure.git

Javascript 相关文章推荐
asp 的 分词实现代码
May 24 Javascript
jQuery 表单验证插件formValidation实现个性化错误提示
Jun 23 Javascript
javascript 弹出的窗口返回值给父窗口具体实现
Nov 23 Javascript
javaScript中slice函数用法实例分析
Jun 08 Javascript
jquery实现input框获取焦点的方法
Feb 06 Javascript
jQuery实现一个简单的轮播图
Feb 19 Javascript
jQuery实现的简单在线计算器功能
May 11 jQuery
vue v-for直接循环数字实例
Nov 07 Javascript
Vuex中实现数据状态查询与更改
Nov 08 Javascript
详解vuejs中执行npm run dev出现页面cannot GET/问题
Apr 26 Javascript
微信小程序入门之指南针
Oct 22 Javascript
详细聊聊vue中组件的props属性
Nov 02 Vue.js
js结合正则实现国内手机号段校验
Jun 19 #Javascript
JavaScript中数据结构与算法(二):队列
Jun 19 #Javascript
JavaScript中数据结构与算法(一):栈
Jun 19 #Javascript
JQuery中模拟image的ajaxPrefilter与ajaxTransport处理
Jun 19 #Javascript
c#程序员对TypeScript的认识过程
Jun 19 #Javascript
JavaScript和JQuery的鼠标mouse事件冒泡处理
Jun 19 #Javascript
TypeScript 中接口详解
Jun 19 #Javascript
You might like
用php实现的下载css文件中的图片的代码
2010/02/08 PHP
PHP文件读写操作之文件读取方法详解
2011/01/13 PHP
ThinkPHP跳转页success及error模板实例教程
2014/07/17 PHP
php中try catch捕获异常实例详解
2014/11/21 PHP
PHP内核学习教程之php opcode内核实现
2016/01/27 PHP
Yii视图CGridView列表用法实例分析
2016/07/12 PHP
php 输入输出流详解及示例代码
2016/08/25 PHP
PHP 构造函数和析构函数原理与用法分析
2020/04/21 PHP
php中yii框架实例用法
2020/12/22 PHP
js操作Xml(向服务器发送Xml,处理服务器返回的Xml)(IE下有效)
2009/01/30 Javascript
extjs表格文本启用选择复制功能具体实现
2013/10/11 Javascript
js为空或不是对象问题的快速解决方法
2013/12/11 Javascript
查询json的数据结构的8种方式简介
2014/03/10 Javascript
Javascript动态创建div的方法
2015/02/09 Javascript
浅析jQuery移动开发中内联按钮和分组按钮的编写
2015/12/04 Javascript
无缝滚动的简单实现代码(推荐)
2016/06/07 Javascript
JS中使用FormData上传文件、图片的方法
2016/08/07 Javascript
使用nodejs+express实现简单的文件上传功能
2017/12/27 NodeJs
JS检测浏览器开发者工具是否打开的方法详解
2020/10/02 Javascript
Vue单页面应用中实现Markdown渲染
2021/02/14 Vue.js
[42:50]NB vs VP 2018国际邀请赛小组赛BO2 第二场 8.18
2018/08/19 DOTA
python 图片验证码代码分享
2012/07/04 Python
django 多数据库配置教程
2018/05/30 Python
pycharm运行程序时在Python console窗口中运行的方法
2018/12/03 Python
python 正则表达式贪婪模式与非贪婪模式原理、用法实例分析
2019/10/14 Python
Python装饰器实现方法及应用场景详解
2020/03/26 Python
Python venv虚拟环境配置过程解析
2020/07/08 Python
Python常用断言函数实例汇总
2020/11/30 Python
简述索引存取方法的作用和建立索引的原则
2013/03/26 面试题
农村产权制度改革实施方案
2014/03/21 职场文书
年度优秀员工获奖感言
2014/08/15 职场文书
2014年重阳节老干部座谈会上的讲话稿
2014/09/25 职场文书
老员工辞职信范文
2015/05/12 职场文书
浅谈redis整数集为什么不能降级
2021/07/25 Redis
解决vue自定义组件@click点击失效问题
2022/04/30 Vue.js
JavaScript正则表达式实现注册信息校验功能
2022/05/30 Java/Android