1.定义
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
本文章以单链表为例
1.1 节点
节点包含两部分,一部分是存储数据元素的数据域,一部分是存储指向下一个节点的指针域,上图,绿色是数据域,蓝色是指针域,他们一起共同构成一个节点。
let Node = function(data){
this.data = data;
this.next = null;
}
let node1 = new Node(1);
let node2 = new Node(2);
let node3 = new Node(3);
node1.next = node2;
node2.next = node3;
console.log(node1.data, 'mmmmm')
console.log(node1.next.data)
console.log(node1.next.next.data)
这就是一个简单的链表。
1.2 首尾节点
顾名思义,链表的第一个节点和最后一个节点
1.3 有头链表和无头链表
无头链表指第一个节点既有指针域也有数据域
有头链表是指第一个节点只有指针域 没有数据域
上图是无头链边,下图是有头链表
下边介绍,均用无头链表
2. 链表的实现
链表有以下方法
- append 添加一个新元素
- insert 在指定位置插入一个元素
- remove 删除指定位置的节点
- remove_head 删除首节点
- remove_tail 删除尾节点
- indexOf 返回指定元素的索引、
- get 返回指定索引位置的元素
- tail 返回尾节点
- head 返回头节点
- length 返回链表的长度
- isEmpty 判断链表是否为空
- clear 清空链表
- prignf 打印整个链表
在代码中,每一步为大家解析
function LinkList(){
var Node = function(data){
this.data = data;
this.next = null;
} let length = 0;
let head = null; // 头节点
let tail = null // 尾节点
// 添加节点
this.append = function(data) {
// 创建新节点
let new_node = new Node(data);
// 如果没有节点,那么头节点就是新节点,尾节点也是新节点
if(head === null) {
head = new_node;
tail = head;
}else {
// 后边只需要在尾节点后边新增加节点
// 然后把新节点点 变成尾节点
tail.next = new_node;
tail = new_node;
}
length +=1;
return true;
}
// 节点打印
this.printf = function(){
let curr_node = head;
// 节点打印 只需要一直 循环,直到节点的指针域不再链接新的节点
while(curr_node) {
console.log(curr_node.data);
curr_node = curr_node.next;
}
}
// 在 指定的位置插入一个元素
this.insert = function(index,data){
if(index < 0 || index > length) {
return null;
}else if (index ===length){
// 此时 只需要在尾节点直接添加新元素
this.append(data)
}else {
let new_node = new Node(data);
if(index === 0) {
// 如果为0 证明是要插到最前边,那么原来的头节点head 就是新元素的下一个节点,
// 新元素 自然就成为新的头节点
new_node.next = head;
head = new_node;
}else {
let curr_node = head;
let insert_index = 1;
// 循环是为了找到插入的位置 我们只需要找到要插入的位置的前一个节点
// 循环结束后 curr_node 就是new_node 的上一个节点
// 我们可以让index 为1 带入想象
while(insert_index < index){
insert_index +=1;
curr_node = curr_node.next;
}
// 此时 curr_node 是 要插入位置的前一个节点
let next_node = curr_node.next; // 将原本当前节点的下一个节点先变量存储 curr_node.next = new_node; new_node.next = next_node; } } length +=1; return true } // 删除指定位置的节点 this.remove = function(index){ if(index < 0 || index >=length) { return null }else { let del_node = null; if(index === 0) { // head 指向下一个节点,那么当前的head 自然就没啦 del_node = head; head = head.next; }else { let curr_node = head; let pre_node = null; let del_index = 0;
// 找到需要删除的位置, del_index 表示被删除节点的所在索引,pre_node 表示被删除
// 节点的上一个节点, curr_node 表示需要被删除的节点
while(del_index < index){
del_index +=1;
pre_node = curr_node;
curr_node = curr_node.next;
}
// 把curr_node 从链表里拿出来
del_node = curr_node;
pre_node.next = curr_node.next;
// 如果删除的是尾节点
if(curr_node.next === null){
tail = pre_node;
}
}
length -= 1;
del_node.next = null;
return del_node.data;
}
}
// 获取指定索引位置的节点,需要传入参数 index
this.get = function(index) {
if(index < 0 || index >= length) {
return null
}
let node_index = 0;
let curr_node = head;
// 遍历节点,找到索引位置的节点
while(node_index < index){
node_index +=1;
curr_node = curr_node.next;
}
return curr_node.data;
}
// 返回指定元素所在的位置 如果链表中没这个元素,返回-1 ,如果存在多个,返回第一个
this.indeOf = function(data){
let curr_node = head;
let index = -1;
while(curr_node) {
index +=1;
if(curr_node.data === data) {
return index
}else {
curr_node = curr_node.next;
}
}
return -1;
}
// 删除 尾节点
this.remove_tail = function() {
return this.remove(length -1)
} // 删除头节点
this.remove_head = function(){
return this.remove(0)
}
// 返回链表的头节点的值
this.head = function(){
return this.get(0)
}
//返回链表尾节点的值
this.tail = function(){
return this.get(length -1)
}
// isEmpty
this.isEmpty = function(){
return length === 0;
}
// 清空链表
this.clear = function(){
head = null;
tail = null;
length = 0
}}
let link = new LinkList();
link.append(1)
link.append(3)
link.append(4)
console.log(link.get(2),'2')
console.log(link.head(),'3')
console.log(link.tail(),'4')
// link.remove(2)
// link.insert(1,8)
console.log(link.indeOf(4),'link.indeOf(2)')
link.printf();
3. 用链表实现栈和队列
实现栈:
function Stack() {
let linklist = new LinkList();
// 从栈顶添加元素
this.push = function(data){
linklist.append(data)
}
// 弹出栈顶元素
this.top = function(){
return linklist.remove_tail();
}
// 返回栈顶元素
this.top = function(){
return linklist.tail()
}
// 返回栈的大小
this.length = function(){
return linklist.length()
}
// 判断栈是否为空
this.isEmpty = function(){
return linklist.isEmpty()
}
// 清空栈
this.clear = function(){
linklist.clear()
}}
实现队列
function Queue(){
let linklist = new LinkList();
// 如队列
this.enqueue = function(data){
linklist.append(data);
}
// 出队列
this.dequeue = function(){
linklist.remove_head()
}
//返回队首
this.head = function(){
return linklist.head()
}
// 返回队尾
this.tail = function(){
return linklist.tail()
}
// size
this.size = function(){
return linklist.length()
}
// clear
this.clear = function(){
linklist.clear()
}
//isEmpty
this.isEmpty = function(){
linklist.isEmpty()
}}
4. 实现翻转链表
1. 使用迭代实现
function reverse_iter(head){
if(!head) {
return null
}
let pre_node = null; // 前一个节点
let curr_node = head; // 当前要翻转的节点
while(curr_node) {
let next_node = curr_node.next; // 记录当前节点的下一个节点
curr_node.next = pre_node; // 当前节点指向前一个节点
pre_node = curr_node; // 节点指针后移 向后滑动
curr_node = next_node; // 节点指针后移 向后滑动
}
// 返回pre_node, 当循环结束时,pre_node指向翻转前链表的最后一个节点
//(翻转前的第一个节点head)的链域为NULL
return pre_node
}
2. 递归实现
function reverise_digui(head){
if(!head){
return null;
}
// 终止条件 : 找到链表的最后一个节点
if(head.next === null ) {
return head
}
// 从下一个节点开始进行翻转 先翻转后边的链表,
//从后边的两个节点开始反转,依次向前
let new_head = reverise_digui(head.next)
head.next.next = head // 将后一个链表节点指向前一个节点
head.next = null; // 将原链表中的前一个节点指向后一个节点的指向关系断开
return new_head;
}