1 链表
链表和数组的特性
数组;
优点:数组的创建通常需要申请一段连续的内存空间。每个元素都是连续紧邻分配,查找的时间复杂度是O(1),效率很高。
缺点:在使用数组时,在开始或特定索引处添加/删除元素这样的操作可能是一项性能较低的任务,因为我们必须移动所有其他元素的索引。
链表:
优点:链表中的元素在内存中不必是连续的空间。可以充分利用计算机的内存, 实现灵活的内存动态管理.链表在插入和删除数据时, 时间复杂度可以达到O(1)。
缺点:链表访问任何一个位置的元素时, 都需要从头开始访问(无法跳过第一个元素访问任何一个元素)。并且无法通过下标直接访问元素,需要从头一个个访问,直到找到对应的问题。
- 数据以查为主,很少涉及到增和删,选择数组。
- 如果数据涉及到频繁的插入和删除,或元素所需分配空间过大,倾向于选择链表。
对链表的理解
- 就像火车一样:火车头是head,火车尾是tail,每一节车厢是节点,车厢中乘客(数据),上一节车厢通过车钩(next)连接下一节车厢(节点),下一节车厢通过车钩(prev)连接上一节车厢。 单向链表: 只能从头遍历到尾或者从尾遍历到头。方便到达下一个节点,但是回到前一个节点是很难的。 双向链表:既可以从头遍历到尾,又可以从尾遍历到头。每次在插入或删除某个节点时,需要处理四个节点的引用,而不是两个, 也就是实现起来要困难一些。并且相当于单向链表, 必然占用内存空间更大一些。
2 单向链表封装
封装了一个单向链表,实现的方法代码中有注释。
//创建Node类
class Node {
constructor(data) {
this.data = data;
this.next = null;
}
}
class LinkedList {
constructor() {
this.head = null;
this.length = 0;
}
//1.向链表尾部添加一个新的项
append(data) {
let node = new Node(data);
if (this.head) {
let current = this.head;
while (current.next) {
current = current.next;
}
current.next = node;
} else {
this.head = node;
}
this.length += 1;
}
//2.toString方法
toString() {
let current = this.head;
let resString = '';
while (current) {
resString += current.data + ' ';
current = current.next;
}
return resString;
}
//3.向链表特定位置插入一个新的项
insert(position, data) {
if (position < 0 || position > this.length) return false;
let node = new Node(data);
if (position == 0) {
node.next = this.head;
this.head = node;
} else {
let current = this.head;
let previous = null;
let index = 0;
while (index++ < position) {
previous = current;
current = current.next;
}
previous.next = node;
node.next = current;
}
this.length += 1;
return true;
}
//4.获取对应位置的元素
get(position) {
if (position < 0 || position >= this.length) return null;
let current = this.head;
let index = 0;
while (index++ < position) {
current = current.next;
}
return current.data;
}
//5.返回元素在列表中的索引,如果没有则返回-1
indexOf(data) {
let current = this.head;
let index = 0;
while (current) {
if (current.data == data) {
return index;
}
current = current.next;
index += 1;
}
return -1;
}
//6.修改某个位置的元素
update(position, newData) {
if (position < 0 || position >= this.length) return false;
let current = this.head;
let index = 0;
while (index++ < position) {
current = current.next;
}
current.data = newData;
return true;
}
//7.从列表的特定位置移除一项
removeAt(position) {
if (position < 0 || position >= this.length) return null;
let current = this.head;
let previous = null;
let index = 0;
if (position == 0) {
this.head = this.head.next;
} else {
while (index++ < position) {
previous = current;
current = current.next;
}
previous.next = current.next;
}
this.length -= 1;
return current.data;
}
//8.从列表中移除一项
remove(data) {
let position = this.indexOf(data);
return this.removeAt(position);
}
//9.size方法
size() {
return this.length;
}
}
//测试代码---------------------------------------------------------------
let list = new LinkedList();
list.append('aaa');
list.append('bbb');
console.log(list.toString());
// console.log(list);
list.insert(0, 'ccc');
// console.log(list);
list.insert(1, 'ddd');
console.log(list);
console.log(list.get(3));
console.log(list.indexOf('aaa'));
console.log(list.indexOf('d'));
console.log(list);
list.update(3, '111');
console.log(list);
list.removeAt(2);
console.log(list.removeAt(2));
console.log(list);
// console.log(list.remove('aaa'));
list.remove('ccc');
console.log(list);
list.append('eee');
console.log(list.size());
3 双向链表封装
直接继承前面单向链表继续写双向链表
class DoublyNode extends Node {
constructor(data) {
super(data);
this.prev = null;
}
}
class DoublyLinkedList extends LinkedList {
constructor() {
super();
this.tail = null;
}
//1 append 向链表末尾添加元素
append(data) {
const newNode = new DoublyNode(data);
if (this.head === null) {
this.head = newNode;
this.tail = newNode;
} else {
this.tail.next = newNode;
newNode.prev = this.tail;
this.tail = newNode;
}
this.length++;
}
//2 insert 向任意位置插入一个元素
insert(position, data) {
if (position < 0 || position > this.length) return false;
let newNode = new DoublyNode(data);
let current = this.head;
if (position === 0) {
if (this.head === null) {
this.head = newNode;
this.tail = newNode;
} else {
newNode.next = this.head;
this.head.prev = newNode;
this.head = newNode;
}
} else if (position === this.length) {
this.tail.next = newNode;
newNode.prev = this.tail;
this.tail = newNode;
} else {
let previous = null;
let index = 0;
while (index++ < position) {
previous = current;
current = current.next;
}
previous.next = newNode;
newNode.next = current;
newNode.prev = previous;
current.prev = newNode;
}
this.length++;
return true;
}
//3 get 获取对应位置的元素
//直接继承单向链表LinkedList的get方法
//4 indexOf 返回元素在列表中的索引,如果没有则返回-1
//直接继承单向链表LinkedList的indexOf方法
//5 remove 在列表的特定位置移除某项
removeAt(position) {
if (position < 0 || position >= this.length) return null;
let current = this.head;
if (position === 0) {
if (this.length === 1) {
this.head = null;
this.tail = null;
} else {
this.head = this.head.next;
this.head.prev = null;
}
} else if (position === this.length - 1) {
current = this.tail;
this.tail = this.tail.prev;
this.tail.next = null;
} else {
let index = 0;
let previous = null;
while (index++ < position) {
previous = current;
current = current.next;
}
previous.next = current.next;
current.next.prev = previous;
}
this.length--;
return current.data;
}
// 6 update 更改某个位置的值,同样直接继承
//7 remove 删除某个元素
//8 toString 继承
// 9 size 继承
}
// 测试代码----------------------------------------------------------------
let dbList = new DoublyLinkedList();
dbList.append('aaa');
dbList.append('bbb');
dbList.append('ccc');
// console.log(dbList );
dbList.insert(0, '111');
dbList.insert(2, '222');
// console.log(dbList );
console.log(dbList.get(0));
console.log(dbList.get(2));
// console.log(dbList);
console.log(dbList.indexOf('222'));
// console.log(dbList);
// console.log(dbList);
console.log(dbList.removeAt(0));
// console.log(dbList);
console.log(dbList.removeAt(0));
dbList.update(0, '555');
dbList.remove('555');
console.log(dbList);