javascript实现数据结构:线性表--线性链表(链式存储结构)

上一节中, 线性表的顺序存储结构的特点是逻辑关系上相邻的两个元素在物理位置上也相邻,因此可以随机存取表中任一元素,它的存储位置可用一个简单,直观的公式来表示。然后,另一方面来看,这个特点也造成这种存储结构的弱点,在做插入或删除操作时,需移动大量元素

链式存储结构,由于它不需要逻辑上相邻的元素在物理位置上也相邻,因此它没有顺序存储结构所具有的弱点,但同时也失去了顺序表可随机存取的优点

线性链表

wiki中的定义:

链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而顺序表相应的时间复杂度分别是O(logn)和O(1)。

使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。

在计算机科学中,链表作为一种基础的数据结构可以用来生成其它类型的数据结构。链表通常由一连串节点组成,每个节点包含任意的实例数据(data fields)和一或两个用来指向明上一个/或下一个节点的位置的链接("links")。链表最明显的好处就是,常规数组排列关联项目的方式可能不同于这些数据项目在记忆体或磁盘上顺序,数据的访问往往要在不同的排列顺序中转换。而链表是一种自我指示数据类型,因为它包含指向另一个相同类型的数据的指针(链接)。链表允许插入和移除表上任意位置上的节点,但是不允许随机存取。链表有很多种不同的类型:单向链表,双向链表以及循环链表。

链表可以在多种编程语言中实现。像LispScheme这样的语言的内建数据类型中就包含了链表的访问和操作。程序语言或面向对象语言,如C/C++和Java依靠易变工具来生成链表。

线性表的链式存储结构的特点是用一组任意的存储单元储存线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的)。因此,为了表示每个数据元素a(i)与其直接后继数据元素a(i+1)之间的逻辑关系,对数据元素a(i)来说,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。这两部分信息组成数据元素a(i)的存储映像,称为结点(node)。它包括两个域:其中存储数据元素信息的域称为数据域;存储直接后继存储位置的域称为指针域,指针域中存储的信息称做指针

又由于此链表的每个结点中只包含一个指针域,故又称线性链表单链表

单链表的整个链表的存取必须从头指针开始进行,头指针指示链表中第一个结点(即第一个数据元素的存储映像)的存储位置。

同时,由于最后一个数据元素没有直接后继,则线性链表中最后一个结点的指针为空null。

链表中最简单的一种是单向链表,它包含两个域,一个信息域和一个指针域。这个链接指向列表中的下一个节点,而最后一个节点则指向一个空值。

javascript实现数据结构:线性表--线性链表(链式存储结构)
一个单向链表包含两个值: 当前节点的值和一个指向下一个节点的链接

一个单向链表的节点被分成两个部分。第一个部分保存或者显示关于节点的信息,第二个部分存储下一个节点的地址。单向链表只可向一个方向遍历。

 // 线性表的单链表存储结构
function LNode(data, node) {
this.data = data;
this.next = node || null;
}

假设p是指向线性表中第i个数据元素(结点a(i))的指针,则p->next是指向第i+1个数据元素(结点a(i+1))的指针。

下面我们来看GetElem在单链表中的实现:

 function getElem(i) {
// 初始化,p指向第一个节点,j为计数器
var p = this.next;
var j = 1;
// 顺指针向后查找,知道p指向第i个元素或p为空
while (p && j < i) {
p = p.next;
++j;
}
// 第i个元素不存在
// 或者取第i个元素
return (!p || j > i) ? null : p.data;
}

单链表的基本操作:

假设我们在线性表的两个数据元素a和b之间插入一个数据元素x,已知p为其单链表存储结构中指向结点a的指针。

插入:

假设s为指向结点x的指针,则可用语句描述:s->next = p->next;  p->next = s;

删除:

假设p为指向结点a的指针,则修改指针的语句为: p->next = p->next->next;

实现:

 function listInsert(i, data) {
var j = 0;
var p = this;
// 寻找第i-1个节点
while (p && j < i - 1) {
p = p.next;
++j;
}
// i < 1或者大于表长+1
if (!p || j > i - 1) return false;
// 生成新节点,插入p节点后面
p.next = new LNode(data, p.next);
return true;
} function listDelete(i) {
var j = 0;
var p = this; while (p.next && j < i - 1) {
p = p.next;
++j;
} if (!p.next || j > i - 1) return false;
var q = p.next;
p.next = q.next;
return q.data;
}

单链表的其他操作

逆位序输入n个元素的值,建立带表头结点的单链线性表L。

function createList_L(n) {
var deferred = require('rsvp').defer();
var l = new LNode();
var count = n;
process.stdin.setEncoding('utf8'); process.stdin.on('data', function handler(data) {
console.log(123);
data = data.replace('\n', '');
l.next = new LNode(data, l.next);
if (!(--count)) {
console.log('pausing');
process.stdin.pause();
deferred.resolve(l);
}
}); return deferred.promise;
}

假设头指针为La和Lb的单链表分别为线性表LA和LB的存储结构,先要归并La和Lb得到单链表Lc:

 function mergeList(a, b) {
var pa = a.next;
var pb = b.next;
// 用a的头结点作为c的头结点
var c = a;
var pc = a; while (pa && pb) {
if (pa.data <= pb.data) {
pc.next = pa;
pc = pa;
pa = pa.next;
} else {
pc.next = pb;
pc = pb;
pb = pb.next;
}
} // 插入剩余段
pc.next = pa ? pa : pb; return c;
}

结构图:

javascript实现数据结构:线性表--线性链表(链式存储结构)

完整代码:

 // 单链表
/*
线性链表存储结构
整个链表的存取必须从头指针开始进行,头指针指示链表中第一个结点(即第一个数据元素的存储映像)的存储位置。
同时,由于最后一个数据元素没有直接后继,则线性链表中最后一个结点的指针为空null。
*/ function LNode(data, node) {
this.data = data;
this.next = node || null;
}
LNode.prototype = {
// 时间复杂度O(n)
getElem: function getElem(i) {
// 初始化,p指向第一个节点,j为计数器
var p = this.next;
var j = 1;
// 顺指针向后查找,知道p指向第i个元素或p为空
while (p && j < i) {
p = p.next;
++j;
}
// 第i个元素不存在
// 或者取第i个元素
return (!p || j > i) ? null : p.data;
},
// 时间复杂度O(n)
listInsert: function listInsert(i, data) {
var j = 0;
var p = this;
// 寻找第i-1个节点
while (p && j < i - 1) {
p = p.next;
++j;
}
// i < 1或者大于表长+1
if (!p || j > i - 1) return false;
// 生成新节点,插入p节点后面
p.next = new LNode(data, p.next);
return true;
},
listDelete: function listDelete(i) {
var j = 0;
var p = this; while (p.next && j < i - 1) {
p = p.next;
++j;
} if (!p.next || j > i - 1) return false;
var q = p.next;
p.next = q.next;
return q.data;
}
}; LNode.createList_L = function createList_L(n) {
var deferred = require('D:\\node\\node_modules\\rsvp').defer();
var l = new LNode();
var count = n;
process.stdin.setEncoding('utf8'); process.stdin.on('data', function handler(data) {
console.log(123);
data = data.replace('\n', '');
l.next = new LNode(data, l.next);
if (!(--count)) {
console.log('pausing');
process.stdin.pause();
deferred.resolve(l);
}
}); return deferred.promise;
}; function deepCopy(obj) {
var newObj = {}; for (var i in obj) {
if (typeof obj[i] === 'object') {
newObj[i] = deepCopy(obj[i]);
} else {
newObj[i] = obj[i];
}
} return newObj;
} // TODO
/*
已知单链线性表a和b的元素按值非递减排列。
归并a和b得到新的单链线性表c,c的元素也按值非递减排列。
*/
LNode.mergeList = function mergeList(a, b) {
var pa = a.next;
var pb = b.next;
// 用a的头结点作为c的头结点
var c = a;
var pc = a; while (pa && pb) {
if (pa.data <= pb.data) {
pc.next = pa;
pc = pa;
pa = pa.next;
} else {
pc.next = pb;
pc = pb;
pb = pb.next;
}
} // 插入剩余段
pc.next = pa ? pa : pb; return c;
}; function log(list) {
var arr = []; do {
arr.push(list.data);
list = list.next;
} while (list); console.log(arr.join(','));
} void function test() {
var a1 = new LNode(1);
a1.listInsert(1, 2);
a1.listInsert(2, 3);
a1.listInsert(1, 4);
console.log(a1.getElem(1));
console.log(a1);
log(a1);
a1.listDelete(1);
console.log('a1 linkList:');
console.log(a1);
log(a1);
/*
LNode.createList_L(5)
.then(function(list){
console.log(list);
});
*/
var a2 = new LNode(3);
a2.listInsert(1, 3);
a2.listInsert(2, 8);
a2.listInsert(1, 4);
a2.listDelete(2);
console.log('a2 linkList');
log(a2); var a3 = LNode.mergeList(a2, a1);
console.log('merging linkLists');
console.log(a3);
log(a3);
}();
上一篇:C语言实现链表(链式存储结构)


下一篇:Hashtable数据存储结构-遍历规则,Hash类型的复杂度为啥都是O(1)-源码分析