| 常用数据结构
- 数组(Array)
- 栈(Stack)先进后出 (LIFO)
- 队列(Queue)先进先出(FIFO)
- 链表(Linked List)
- 树(Tree)
- 图(Graph)
- 堆(Heap)
- 散列表(Hash 哈希表
链表
与数组不同的是,组成链表的格子不是连续的
每个不连续的格子叫结点,多个结点就构成了链表
计算机需要知道分散结点的信息,这就是链表的关键了:
- 每个结点保存着数据,
- 还保存着链表里的下一结点的内存地址(链)
用来指示下一结点的内存地址的额外数据,被称为链
链表和数组的区别
- 数组是连续存放,链表可连续也可不连续
- 数组操作元素比较麻烦,链表比较容易,只需改指针
- 数组查找块 (时间复杂度为
O(1)),链表查找慢(O(n)) - 链表从堆中分配空间, 自由度大但申请管理比较麻烦
- 链表的最坏情况和最好情况与数组刚好相反
应用
如果需要快速访问数据,很少插入和删除元素,就应该用数组。
如果需要经常插入和删除元素你就需要用链表。
js实现链表基本功能
// 实现结点(包含结点数据、下一个结点的引用)
class Node {
nextNode = null;
constructor(data) {
this.data = data;
}
}
// 实例链表包装
class CreateLinkedList {
firstNode = null;
constructor(firstNode) {
this.firstNode = firstNode;
}
// 根据索引找数据
find(index) {
this.currentNode = this.firstNode;
this.currentIndex = 0;
// 可见,链表的查找的时间复杂度是O(n)
while (this.currentIndex < index) {
if (this.currentNode.nextNode) {
this.currentNode = this.currentNode.nextNode;
this.currentIndex++;
} else {
return null;
}
}
return this.currentNode.data;
}
// 根据数据查找索引
findIndex(value) {
this.currentNode = this.firstNode;
this.currentIndex = 0;
while (true) {
if (this.currentNode.data == value) {
return this.currentIndex;
}
if (!this.currentNode.nextNode) {
return -1;
}
this.currentIndex++;
this.currentNode = this.currentNode.nextNode;
}
}
// 插入
insertAtIndex(index, value) {
// 创建一个新结点
const newNode = new Node(value)
if (index === 0) {
newNode.nextNode = this.firstNode
return this.firstNode = newNode
}
this.currentNode = this.firstNode;
this.currentIndex = 0;
// 获取前一项
while (this.currentIndex < index -1) {
if (!this.currentNode.nextNode) {
break;
}
this.currentIndex++;
this.currentNode = this.currentNode.nextNode;
}
newNode.nextNode = this.currentNode.nextNode
this.currentNode.nextNode = newNode
return this;
}
// 删除
deleteAtIndex(index) {
if (index === 0) {
return this.firstNode = this.firstNode.nextNode
}
this.currentNode = this.firstNode;
this.currentIndex = 0;
// 获取删除的那项前一项
while (this.currentIndex < index -1) {
if (!this.currentNode.nextNode) {
return -1;
// break;
}
this.currentIndex++;
this.currentNode = this.currentNode.nextNode;
}
// 被删除的那一项
let deleteNode = this.currentNode.nextNode;
// 被删除的那一项的后一项
let afterNode = deleteNode.nextNode
// 前一项和后一项相连
this.currentNode.nextNode = afterNode;
return deleteNode
}
}
let node1 = new Node("node1");
let node2 = new Node("node2");
let node3 = new Node("node3");
let node4 = new Node("node4");
node1.nextNode = node2;
node2.nextNode = node3;
node3.nextNode = node4;
const linkedList = new CreateLinkedList(node1);
console.log(linkedList); // 链表
双向链表
数组的时间复杂度插入是O(1),删除是O(N);
链表则反过来,分别是O(N)和O(1)。
它们总有一种操作是 O(1),另一种是O(N)
基于以上分析,似乎用数组还是链表都无所谓
双向链表是链表的变种,就能使队列的插入和删除的时间复杂度都为O(1)
双向链表跟链表差不多,只是它每个结点都含有两个链
一个指向下一结点,另一个指向前一结点。
它还能直接访问第一个和最后一个结点。
js实现双向链表
class Node {
pre = null;
next = null;
constructor(data) {
this.data = data;
}
}
class DoubleLinkedList {
tail = null;
length = 0;
head = null;
// 常见的方法
// 1.append方法
append = (data) => {
//1. 根据data创建新节点
let newNode = new Node(data);
// 2.添加节点
// 情况1:添加的是第一个节点
if (this.length == 0) {
this.head = newNode;
this.tail = newNode;
} else {
// 情况2:添加的不是第一个节点
newNode.pre = this.tail;
this.tail.next = newNode;
this.tail = newNode;
}
// 3.length+1
this.length += 1;
};
// 2.将链表转变为字符串形式
// 2.1 toString方法
toString = () => {
return this.backwardString();
};
// 2.2 forwardString方法
forwardString = () => {
// 1.定义变量
let current = this.tail;
let resStr = "";
// 2.依次向前遍历,获取每一个节点
while (current) {
resStr += current.data + "**";
current = current.pre;
}
return resStr;
};
// 2.3 backwardString方法
backwardString = () => {
// 1.定义变量
let current = this.head;
let resStr = "";
// 2.依次向后遍历,获取每一个节点
while (current) {
resStr += current.data + "--";
current = current.next;
}
return resStr;
};
// 3.insert方法
insert = (position, data) => {
// 1.越界判断
if (position < 0 || position > this.length) return false;
// 2.根据data创建新的节点
let newNode = new Node(data);
// 3.插入新节点
// 原链表为空
// 情况1:插入的newNode是第一个节点 原链表为空
if (this.length == 0) {
this.head = newNode;
this.tail = newNode;
} else {
// 原链表不为空
// 情况2:position == 0
if (position == 0) {
this.head.pre = newNode;
newNode.next = this.head;
this.head = newNode;
} else if (position == this.length) {
// 情况3:position == this.length
this.tail.next = newNode;
newNode.pre = this.tail;
this.tail = newNode;
} else {
// 情况4:0 < position < this.length
let current = this.head;
let index = 0;
while (index++ < position) {
current = current.next;
}
//修改pos位置前后节点变量的指向
newNode.next = current;
newNode.pre = current.pre;
current.pre.next = newNode;
current.pre = newNode;
}
}
// 4.length+1
this.length += 1;
return true; // 返回true表示插入成功
};
// 4.get方法
get = (position) => {
// 1.越界判断
if (position < 0 || position >= this.length) {
//获取元素时position不能等于length
return null;
}
// 2.获取元素
let current = null;
let index = 0;
// this.length / 2 > position:从头开始遍历
if (this.length / 2 > position) {
current = this.head;
while (index++ < position) {
current = current.next;
}
return current.data;
} else {
// this.length / 2 =< position:从尾开始遍历
current = this.tail;
index = this.length - 1;
while (index-- > position) {
current = current.pre;
}
}
return current.data;
};
// 5.indexOf方法
indexOf = (data) => {
// 1.定义变量
let current = this.head;
let index = 0;
// 2.遍历链表,查找与data相同的节点
while (current) {
if (current.data == data) {
return index;
}
current = current.next;
index++;
}
return -1;
};
// 6.update方法
update = (position, newData) => {
// 1.越界判断
if (position < 0 || position >= this.length) return false;
// 2.寻找正确的节点
let index = 0;
let current = this.head;
if (this.length / 2 > position) {
while (index++ < position) {
current = current.next;
}
} else {
current = this.tail;
index = this.length - 1;
while (index-- > position) {
current = current.pre;
}
}
// 3.修改找到节点的data
current.data = newData;
return true; //表示成功修改
};
// 7.removeAt方法
removeAt = (position) => {
// 1.越界判断
if (position < 0 || position >= this.length) return null;
// 2.删除节点
// 当链表中length == 1
// 情况1:链表只有一个节点
let current = this.head; //定义在最上面方便以下各种情况返回current.data
if (this.length == 1) {
this.head = null;
this.tail = null;
} else {
// 情况2:删除第一个节点
if (position == 0) {
this.head.next.pre = null;
this.head = this.head.next;
} else if (position == this.length - 1) {
// 情况3:删除最后一个节点
current = this.tail; // 该情况下返回被删除的最后一个节点
this.tail.pre.next = null;
this.tail = this.tail.pre;
} else {
let index = 0;
while (index++ < position) {
current = current.next;
}
current.pre.next = current.next;
current.next.pre = current.pre;
}
}
// 3.length -= 1
this.length -= 1;
return current.data; // 返回被删除节点的数据
};
/*--------------------其他方法-------------------*/
// 8.remove方法
remove = (data) => {
// 1.根据data获取下标值
let index = this.indexOf(data);
// 2.根据index删除对应位置的节点
return this.removeAt(index);
};
// 9.isEmpty方法
isEmpty = () => {
return this.length == 0;
};
// 10.size方法
size = () => {
return this.length;
};
// 11.getHead方法:获取链表的第一个元素
getHead = () => {
return this.head.data;
};
// 12.getTail方法:获取链表的最后一个元素
getTail = () => {
return this.tail.data;
};
}
let list = new DoubleLinkedList();
list.append("aaa");
list.append("bbb");
list.append("ccc");
list.append("ddd");
console.log(list);