链表相对于数组来说,要复杂的多,首先,链表不需要连续的内存空间,它是由一组零散的内存块透过指针连接而成,所以,每一个块中必须包含当前节点内容以及后继指针。最常见的链表类型有单链表、双链表以及循环链表。
单链表:
只有一个方向,next依次往后
//初始化一个链表
function List() {
//节点,包含自身和next
let Node = function (element) {
this.element = element;
this.next = null;
}
// 初始头节点为 null
let head = null
// 链表长度
let length = 0
// 查找节点
this.getList = function () { return head }
// 链表是否为空
this.isEmpty = function () {
if(!head) return true
return false
}
// 获取链表的size
this.size = function () {
return length
}
}
1.添加节点:
解题思路:需要添加在链表末尾,所以要找到next为null的节点上去添加,时间复杂度为O(n)
// 需要添加在链表末尾,所以要找到next为null的节点上去添加,时间复杂度为O(n)
this.append = function (element) {
let node = new Node(element), p = head;
if(!head) { //如果head不存在
head = node;
} else {
while(p.next) {
p = p.next;
}
p.next = node ;
}
length += 1;
console.log(head)
}
2.查找节点:
解题思路: 按照单链表依次往下找,找到该元素的值与需要查找的元素相等。
// 查找节点,可见查找速度的时间复杂度由元素在链表那个位置决定,最好是O(1),最坏是O(n)
this.search = function (element) {
let p = head;
if(!p) return null
while(p) {
if(p.element === element) return true
p = p.next
}
return false
}
3.插入节点:
解题思路:和添加节点类似,只不过,需要知道插入的位置,然后将节点插入到该位置上的元素的next,再将后面的元素添加到插入的元素上的next。
//最好是在O(1),最坏是O(n)。
this.insert = function (position, element) {
let node = new Node(element)
if(position >= 0 && position<= length) {
if(position === 0) {
node.next = head;
head = node
} else {
let index = 0;
let prev = head, cur = head;
while(index < position) {
prev = cur;
cur = cur.next
index++
}
prev.next = node;
node.next = cur
}
length += 1
return
} else {
return null
}
}
4.删除节点:
解题思路:查找到需要删除的节点之后,将节点的next赋值给该节点上一个节点的next。
// 查找到需要删除的节点,最好是删除头结点,时间复杂度O(1), 最坏是删除尾节点,O(n)
this.remove = function (element) {
let p = head, prev = head;
if(!p) return
while(p) {
if(p.element === element){
p = p.next
prev.next = p;
length -= 1;
} else {
prev = p;
p = p.next
}
}
}
测试一下
var node = new List()
node.size() //0
node.append(4) //
node.append(5)
node.append(7)
node.insert(0,3)
console.log(node.size()) // 4
node.insert(3,6)
双链表
单链表只有一个next往后,那么双链表,则是有一个prev和一个next.
function doubleList(){
let Node = function(element) {
this.element = element;
this.prev = null;
this.next = null;
}
// 初始头节点为 null
let head = null;
// 新增尾结点
let tail = null;
// 链表长度
let length = 0
// 获取链表
this.getList = function () { return head }
}
1.插入节点,包含了添加节点。
解题思路:和单链表类似,在某个链表里有的位置进行插入的时候,需要对prev和next做处理。
this.insert = function(position, element) {
let node = new Node(element);
if(position >=0 && position<= length) {
let prev = head,
curr = head,
index = 0;
if(position === 0) {
if(!head) { //与单链表不同,首尾节点都要赋值
head = node;
tail = node;
} else {
//双向
node.next = head;
head.prev = node;
//节点变化,node成为新的头结点
head = node;
}
} else if(position === length) { //在尾结点插入,即添加节点的做法
tail.next = node;
node.prev = taile;
//节点变化,node成为新的尾结点
tail = node;
} else {
while(index < position) {
prev = curr;
curr = curr.next;
index++;
}
//插入到prev后,curr前
prev.next = node;
node.next = curr;
curr.prev = node;
node.prev = prev;
}
length += 1;
return true
} else {
return false
}
}
2.删除节点
解题思路:删除某个位置的节点,就是依次遍历,将删除位置的节点.next.prev纸箱该位置的前一个节点的next
this.remove = function(position) {
if(position >=0 && position< length && length > 0) {
let prev = head,
curr = head,
index = 0;
if(position === 0) {// 移除头节点
if(length === 1) { // 仅有一个节点
head = null
tail = null
} else {
head = head.next;
head.prev = null;
}
} else if(position === length-1) {// 移除尾结点,将尾结点前移
curr = tail;
tail = curr.prev;
tail.next = null;
} else {
while(index < position) {
prev = curr;
curr = curr.next;
index++;
}
console.log('当前prev', prev)
console.log('当前curr', curr)
// 移除curr
prev.next = curr.next
curr.next.prev = prev
}
length -= 1
return curr.element
}
}
}
循环单链表与双链表
顾名思义:当形成一个环形链表时,即尾结点最后指向头结点,形成环。
function circleList(){
let Node = function(element) {
this.element = element;
this.next = null;
}
let head = null;
let length = 0;
//和单链表类似,唯一不同的是:循环单链表的循环结束条件为 index++ < length。
//同理删除也是
this.search = function(element){ //与单链表不同的是需要一个结束条件,因为没有指向null
let p = head , index = 0;
if(!p) return null
while(index++ < length) { //当index小于length,否则就查找完了
if(p.element === element) return true
p = p.next
//index++;
}
return false
}
}
1.插入节点
解题思路:与单链表插入类似,只是当从链表头(尾)插入的时候,需要改变尾结点的指向到头结点。
this.insert = function(position, element) {
let node = new Node(element)
if(position>=0 && position<=length) {
let prev = head,
curr = head,
index = 0;
if(position === 0) { //与单链表插入不同
while(index < length) { //目的是找到最后一个节点
prev = curr;
curr = curr.next;
index++;
}
prev.next = node; //prev为最后一个节点,最后一个节点指向头结点,此时头结点为插入的node。
node.next = curr; //curr为之前的头结点
head = node; //最后把head变为头结点
} else {
while(index < position) {
prev = curr;
curr = curr.next;
index++;
}
prev.next = node //prev为position位置处的前一个节点
node.next = node.next = curr ? head: curr;
}
length++;
} else {
return null
}
};
2.查询单链表
解题思路:和单链表类似,唯一不同就是结束条件为从头遍历的节点数小于链表长度
this.search = function(element){ //与单链表不同的是需要一个结束条件,因为没有指向null
let p = head , index = 0;
if(!p) return null
while(index++ < length) { //当index小于length,否则就查找完了
if(p.element === element) return true
p = p.next
//index++;
}
return false
}
循环双向链表就不说了,本质也是双向链表,也是尾结点的next指向了头结点,头结点的pre指向了尾结点。