上一篇文章中介绍了几种常见链表的含义,今天介绍下如何写出正确的链表代码。node
咱们通常设计的链表有两个类。Node
类用来表示节点,LinkedList
类提供了一些辅助方法,好比说结点的增删改查,以及显示列表元素等方法。 接下来看看如何用 js
代码表示一个链表。git
代码演示:github
{
var Node = function(data) {
this.data = data;
this.next = null;
};
var node1 = new Node(1);
var node2 = new Node(2);
var node3 = new Node(3);
node1.next = node2;
node2.next = node3;
console.log(node1.data);
console.log(node1.next.data);
console.log(node1.next.next.data);
}
复制代码
Node
类包含两个属性:data
用来保存节点上的数据,next
用来保存指向下一个节点的连接。算法
{
var LList = function() {
this.head = new Node('head');
this.find = find;
this.insert = insert;
this.remove = remove;
this.display = display;
}
}
复制代码
LList
类提供了对链表进行操做的方法。该类中使用一个 Node
类来保存链表中的头结点。当有新元素插入时,next
就会指向新的元素。bash
怎么样,简单吧,你已经学会链表了数据结构
但,这只是基本的链表表示法,水还很深。app
把上一篇文章中的单链表图搬过来,方便参考post
这里的指针或者引用,他们的意思都是同样的,都是存储所指对象的内存地址。this
将某个变量赋值给指针,实际上就是将某个变量的地址赋值给指针,或者反过来讲,指针中存储了这个变量的内存地址,指向了这个变量,经过指针就能找到这个变量。spa
看下面的伪代码表示什么意思:
p -> next = q;
复制代码
这行代码就是说 p 结点中的 next 指针存储了 q 结点的内存地址。
再看下面的代码表示什么:
p -> next = p -> next -> next;
复制代码
这行代码表示,p 结点的 next 指针存储了 p 结点的下下一个结点的内存地址。
如今应该能有所体会指针或者引用的概念了吧。
在写链表代码的时候,尤为是咱们的指针,会不断的改变,指来指去的。因此在写的时候,必定注意不要弄丢了指针。
以下图中单链表的插入操做
咱们但愿在结点 B 和相邻的结点 C 之间插入 D 结点。假设当前指针 p 指向 B 结点。若是你将代码实现成下面这个样子,就会发生指针丢失和内存泄露。
// 伪代码
p -> next = d; // 将 p 的 next 指针指向 D 结点;
d -> next = p -> next // 将 D 的结点的 next 指针指向 C? 结点。
复制代码
咱们来分析下:p -> next 指针在完成第一步操做后,已经再也不指向结点 C 了,而是指向新增长的结点 D。第二行的代码至关于将 D 赋值给 d->next,本身指向本身。所以,整个链表也就被截断了。
因此咱们添加结点时,必定要注意操做的顺序,要先将结点 D 的 next 指针指向结点 C,再把结点 B 的指针指向 D,这样才不会丢失指针。对于刚才的那段代码,你知道怎么修改才是正确的了吧。
首先来回顾下刚才所说的单链表的插入操做。若是在 p 结点后面增长一个新的结点,只须要关注如下两步便可。
new_node -> next = p -> next;
p -> next = new_node;
复制代码
可是,当咱们向一个空链表中插入第一个结点时,就须要特殊处理了。当链表为空时,也就是链表的head为空,那直接赋值便可,以下:
if(head == null) {
head = new_node;
}
复制代码
看一段完整的添加节点代码:
// 添加一个新结点 tail:表示尾结点
append(data) {
const node = new Node(data);
if (!this.head) {
this.head = node;
this.tail = this.head;
} else {
this.tail.next = node;
this.tail = node;
}
}
复制代码
若是头结点不存在的话,头结点等于尾结点。若是头结点存在的话,利用尾结点来扩充链表的数据,别忘了再移动 tail
成为尾结点。
再来看单链表结点的删除操做。若是在p结点后删除一个结点,只须要关注一步便可:
p -> next = p -> next -> next;
复制代码
可是,当链表中只剩一个结点head时,也须要特殊处理才能够,以下:
if(head -> next == null){
head = null;
}
复制代码
删除的代码逻辑请查看 github-链表-remove
我提供的方法比较繁琐,当阅读了《数据结构与算法JavaScript描述》的时候,发现书上写的方法特别简洁。在此分享一下。
如下是此书中的删除结点代码:
{
/* 首先根据要删除的元素,检查每个结点的下一个结点中是否存储着要删除的数据。 若是找到,则返回该结点,即前一个结点。 */
function findPvevious(item) {
var currNode = this.head;
while(!(currNode.next == null) && (currNode.next.element != item)) {
currNode = currNode.next;
}
return currNode;
}
// 而后找到前一个结点后,利用上文提到的单链表删除操做进行删除。
function remove(item) {
var prevNode = this.findPvevious(item);
if(!(prevNode.next == null)) {
prevNode.next = prevNode.next.next;
}
}
}
复制代码
因此写链表代码时,要常常注意边界条件是否考虑到了:
好比一些单链表的增删改查等,指针老是不断的改变。这个时候若是大脑思考不过来的话,能够简单的画个示意图辅助一下。好比说单链表的增长结点操做,能够画出增长先后的链表变化。
请前往 github 查看 链表常见的方法
《数据结构与算法JavaScript描述》
链表这种数据结构,确实比较容易懂,可是想写出好相关的操做代码,确实不易,指针或者引用也是个人薄弱环节,指来指去便记不清该怎么指啦!动物的关节对于动物来说很是的重要,指针感受就是链表的关节,链表像一辆火车,每一个车箱的链接全靠车箱间的链接轴。