8种常见的JavaScript数据结构
目录
数据结构是Javascript中的格式,有助于以更有效的方式访问数据,并在需要时进行修改。这是通过数据结构实现的,因为它们允许你以某种方式组织、存储和管理数据。在javascript中,有一系列的数据结构,了解每种数据结构有助于用户了解何时根据他们的要求选择一种特定的结构而不是另一种。如果你正在使用Javascript,这里有8种你必须了解的最常见的数据结构。
JavaScript中8种常见的数据结构
1.堆栈
堆栈数据结构保存着一个元素列表,其工作原理是后进先出(LIFO)。这意味着最近添加的元素也是最先被删除的元素。在堆栈顶部有两个主要的操作,即推和弹。使用push方法,你可以向数组的末端添加一个或多个元素。然而,用pop方法,你可以摆脱数组末端的元素,然后将其返回给调用者。
function Stack() {
this.count = 0;
this.storage = {};
this.push = function (value) {
this.storage\[this.count\] = value;
this.count++;
}
this.pop = function () {
if (this.count === 0) {
return undefined;
}
this.count--;
var result = this.storage\[this.count\];
delete this.storage\[this.count\];
return result;
}
this.peek = function () {
return this.storage\[this.count - 1\];
}
this.size = function () {
return this.count;
}
}
2.队列
虽然在概念上与堆栈相似,但队列与堆栈不同,堆栈是根据最近输入的元素进行处理,而队列是按照输入的顺序处理元素。因此,当你想按照收到的顺序存储请求时,你可以使用队列。举个例子,这里是利用一个数组来运行一个队列。
function Queue() {
var collection = \[\];
this.print = function () {
console.log(collection);
}
this.enqueue = function (element) {
collection.push(element);
}
this.dequeue = function () {
return collection.shift();
}
this.front = function () {
return collection\[0\];
}
this.isEmpty = function () {
return collection.length === 0;
}
this.size = function () {
return collection.length;
}
}
3.优先级队列
优先级队列用于为每个元素分配优先级。这些元素按照优先级进行排序。
function PriorityQueue() {
...
this.enqueue = function (element) {
if (this.isEmpty()) {
collection.push(element);
} else {
var added = false;
for (var i = 0; i < collection.length; i++) {
if (element\[1\] < collection\[i\]\[1\]) {
collection.splice(i, 0, element);
added = true;
break;
}
}
if (!added) {
collection.push(element);
}
}
}
}
4.链接列表
链接列表使用一个引用系统,而不是使用内存中数据的物理位置。链接列表将元素存储在节点中。这些节点包含一个指向后续节点的指针,这在所有节点之间建立了一个链接。通过链接列表系统,你可以有效地插入和删除项目,而不需要重新组织。下面是一个使用节点的链接列表的例子-
/\*\* Node in the linked list \*\*/
function Node(element) {
// Data in the node
this.element = element;
// Pointer to the next node
this.next = null;
}
function LinkedList() {
var length = 0;
var head = null;
this.size = function () {
return length;
}
this.head = function () {
return head;
}
this.add = function (element) {
var node = new Node(element);
if (head == null) {
head = node;
} else {
var currentNode = head;
while (currentNode.next) {
currentNode = currentNode.next;
}
currentNode.next = node;
}
length++;
}
this.remove = function (element) {
var currentNode = head;
var previousNode;
if (currentNode.element === element) {
head = currentNode.next;
} else {
while (currentNode.element !== element) {
previousNode = currentNode;
currentNode = currentNode.next;
}
previousNode.next = currentNode.next;
}
length--;
}
this.isEmpty = function () {
return length === 0;
}
this.indexOf = function (element) {
var currentNode = head;
var index = -1;
while (currentNode) {
index++;
if (currentNode.element === element) {
return index;
}
currentNode = currentNode.next;
}
return -1;
}
this.elementAt = function (index) {
var currentNode = head;
var count = 0;
while (count < index) {
count++;
currentNode = currentNode.next;
}
return currentNode.element;
}
this.addAt = function (index, element) {
var node = new Node(element);
var currentNode = head;
var previousNode;
var currentIndex = 0;
if (index > length) {
return false;
}
if (index === 0) {
node.next = currentNode;
head = node;
} else {
while (currentIndex < index) {
currentIndex++;
previousNode = currentNode;
currentNode = currentNode.next;
}
node.next = currentNode;
previousNode.next = node;
}
length++;
}
this.removeAt = function (index) {
var currentNode = head;
var previousNode;
var currentIndex = 0;
if (index < 0 || index >= length) {
return null;
}
if (index === 0) {
head = currentIndex.next;
} else {
while (currentIndex < index) {
currentIndex++;
previousNode = currentNode;
currentNode = currentNode.next;
}
previousNode.next = currentNode.next;
}
length--;
return currentNode.element;
}
}
5.集合
Set是一个定义明确的对象的集合,它可以让你把数据添加到一个容器中。虽然与数组有些相似,但集合没有索引,也不允许重复元素。我们可以在ES6中声明MySet来区分集合,下面的例子演示了这一点。
function MySet() {
var collection = \[\];
this.has = function (element) {
return (collection.indexOf(element) !== -1);
}
this.values = function () {
return collection;
}
this.size = function () {
return collection.length;
}
this.add = function (element) {
if (!this.has(element)) {
collection.push(element);
return true;
}
return false;
}
this.remove = function (element) {
if (this.has(element)) {
index = collection.indexOf(element);
collection.splice(index, 1);
return true;
}
return false;
}
this.union = function (otherSet) {
var unionSet = new MySet();
var firstSet = this.values();
var secondSet = otherSet.values();
firstSet.forEach(function (e) {
unionSet.add(e);
});
secondSet.forEach(function (e) {
unionSet.add(e);
});
return unionSet; }
this.intersection = function (otherSet) {
var intersectionSet = new MySet();
var firstSet = this.values();
firstSet.forEach(function (e) {
if (otherSet.has(e)) {
intersectionSet.add(e);
}
});
return intersectionSet;
}
this.difference = function (otherSet) {
var differenceSet = new MySet();
var firstSet = this.values();
firstSet.forEach(function (e) {
if (!otherSet.has(e)) {
differenceSet.add(e);
}
});
return differenceSet;
}
this.subset = function (otherSet) {
var firstSet = this.values();
return firstSet.every(function (value) {
return otherSet.has(value);
});
}
}
6.哈希表
哈希表一般用于Object数据结构、Map或Dictionary中,因为它能以极快的速度通过一个键查询一个值。当你使用哈希表时,一个哈希函数被用来将键转换成一个数字列表,这被称为哈希。一个哈希值代表相应的键的值。一个哈希值指向表的一个较小的子组,这被称为存储桶。然后,存储桶被搜索到最初输入的键。然后,它返回与该键相关的值。下面是一个Javascript中的哈希表的简单例子。
function hash(string, max) {
var hash = 0;
for (var i = 0; i < string.length; i++) {
hash += string.charCodeAt(i);
}
return hash % max;
}
function HashTable() {
let storage = \[\];
const storageLimit = 4;
this.add = function (key, value) {
var index = hash(key, storageLimit);
if (storage\[index\] === undefined) {
storage\[index\] = \[
\[key, value\]
\];
} else {
var inserted = false;
for (var i = 0; i < storage\[index\].length; i++) {
if (storage\[index\]\[i\]\[0\] === key) {
storage\[index\]\[i\]\[1\] = value;
inserted = true;
}
}
if (inserted === false) {
storage\[index\].push(\[key, value\]);
}
}
}
this.remove = function (key) {
var index = hash(key, storageLimit);
if (storage\[index\].length === 1 && storage\[index\]\[0\]\[0\] === key) {
delete storage\[index\];
} else {
for (var i = 0; i < storage\[index\]; i++) {
if (storage\[index\]\[i\]\[0\] === key) {
delete storage\[index\]\[i\];
}
}
}
}
this.lookup = function (key) {
var index = hash(key, storageLimit);
if (storage\[index\] === undefined) {
return undefined;
} else {
for (var i = 0; i < storage\[index\].length; i++) {
if (storage\[index\]\[i\]\[0\] === key) {
return storage\[index\]\[i\]\[1\];
}
}
}
}
}
7.树
树是一种非线性的多层数据结构,事实证明它在一些操作上是非常有效的,比如插入和搜索。一棵树有一个根节点,其他节点从这个根节点开始分支。根节点有各种子节点,这些子节点有对所有元素的引用。然后又有更多的子节点从子节点分支出来,这样继续下去。当一个节点有链接的子节点时,它被称为内部节点。而没有子节点的节点则被称为外部节点。树状结构在存储分层数据时很有帮助,它可以帮助人们在需要对数据进行排序时进行搜索。
class Node {
constructor(data, left = null, right = null) {
this.data = data;
this.left = left;
this.right = right;
}
}
class BST {
constructor() {
this.root = null;
}
add(data) {
const node = this.root;
if (node === null) {
this.root = new Node(data);
return;
} else {
const searchTree = function (node) {
if (data < node.data) {
if (node.left === null) {
node.left = new Node(data);
return;
} else if (node.left !== null) {
return searchTree(node.left);
}
} else if (data > node.data) {
if (node.right === null) {
node.right = new Node(data);
return;
} else if (node.right !== null) {
return searchTree(node.right);
}
} else {
return null;
}
};
return searchTree(node);
}
}
findMin() {
let current = this.root;
while (current.left !== null) {
current = current.left;
}
return current.data;
}
findMax() {
let current = this.root;
while (current.right !== null) {
current = current.right;
}
return current.data;
}
find(data) {
let current = this.root;
while (current.data !== data) {
if (data < current.data) {
current = current.left
} else {
current = current.right;
}
if (current === null) {
return null;
}
}
return current;
}
isPresent(data) {
let current = this.root;
while (current) {
if (data === current.data) {
return true;
}
if (data < current.data) {
current = current.left;
} else {
current = current.right;
}
}
return false;
}
remove(data) {
const removeNode = function (node, data) {
if (node == null) {
return null;
}
if (data == node.data) {
// no child node
if (node.left == null && node.right == null) {
return null;
}
// no left node
if (node.left == null) {
return node.right;
}
// no right node
if (node.right == null) {
return node.left;
}
// has 2 child nodes
var tempNode = node.right;
while (tempNode.left !== null) {
tempNode = tempNode.left;
}
node.data = tempNode.data;
node.right = removeNode(node.right, tempNode.data);
return node;
} else if (data < node.data) {
node.left = removeNode(node.left, data);
return node;
} else {
node.right = removeNode(node.right, data);
return node;
}
}
this.root = removeNode(this.root, data);
}
}
8.三角形
Trie是一种搜索树,它也被称为 "前缀树"。它对于以循序渐进的方式存储数据很有用。当你使用Trie时,你可以存储词汇以进行快速搜索。它对自动完成功能特别有用。树中的步骤由节点表示。下面是一个Trie结构的例子-
/\*\* Node in Trie \*\*/
function Node() {
this.keys = new Map();
this.end = false;
this.setEnd = function () {
this.end = true;
};
this.isEnd = function () {
return this.end;
}
}
function Trie() {
this.root = new Node();
this.add = function (input, node = this.root) {
if (input.length === 0) {
node.setEnd();
return;
} else if (!node.keys.has(input\[0\])) {
node.keys.set(input\[0\], new Node());
return this.add(input.substr(1), node.keys.get(input\[0\]));
} else {
return this.add(input.substr(1), node.keys.get(input\[0\]));
}
}
this.isWord = function (word) {
let node = this.root;
while (word.length > 1) {
if (!node.keys.has(word\[0\])) {
return false;
} else {
node = node.keys.get(word\[0\]);
word = word.substr(1);
}
}
return (node.keys.has(word) && node.keys.get(word).isEnd()) ? true : false;
}
this.print = function () {
let words = new Array();
let search = function (node = this.root, string) {
if (node.keys.size != 0) {
for (let letter of node.keys.keys()) {
search(node.keys.get(letter), string.concat(letter));
}
if (node.isEnd()) {
words.push(string);
}
} else {
string.length > 0 ? words.push(string) : undefined;
return;
}
};
search(this.root, new String());
return words.length > 0 ? words : null;
}
}
9.图
图形数据结构允许节点有零个或多个相邻元素。在图中,有时也被称为网络,有一些节点的集合,这些节点有链接或边。根据这些联系是否有方向,有两组图--有向图和无向图。
图可以用两种方式表示。一种被称为相邻列表,其中左侧由所有节点组成,右侧由连接的节点组成。在图的另一种表现形式中,被称为邻接矩阵,节点被排列成行和列。节点之间的关系由行和列的交点表示。而0表示没有联系,1表示有联系,>1表示不同的权重。
广度优先搜索法一般用于查询图中的节点,通过搜索整个树状网络来进行查询。下面是一个BFS的例子,用于Javascript-中的这种操作
function bfs(graph, root) {
var nodesLen = {};
for (var i = 0; i < graph.length; i++) {
nodesLen\[i\] = Infinity;
}
nodesLen\[root\] = 0;
var queue = \[root\];
var current;
while (queue.length != 0) {
current = queue.shift();
var curConnected = graph\[current\];
var neighborIdx = \[\];
var idx = curConnected.indexOf(1);
while (idx != -1) {
neighborIdx.push(idx);
idx = curConnected.indexOf(1, idx + 1);
}
for (var j = 0; j < neighborIdx.length; j++) {
if (nodesLen\[neighborIdx\[j\]\] == Infinity) {
nodesLen\[neighborIdx\[j\]\] = nodesLen\[current\] + 1;
queue.push(neighborIdx\[j\]);
}
}
}
return nodesLen;
}
数据结构面试题
1.文件结构和存储结构的区别是什么?
文件结构表示数据进入设备中的二级或辅助存储器,如笔式驱动器和硬盘。所存储的数据保持不变,除非是手动取下。另一方面,存储结构的数据被存储在主存储器中,如随机存取存储器中。当使用该数据的功能被完全执行后,数据会被自动删除。
2.线性和非线性数据结构的区别是什么?
线性数据结构和非线性数据结构的不同之处在于,对于线性数据结构来说,数据结构元素成为一个线性列表或一个序列。然而,对于非线性数据结构来说,有一个节点的遍历。线性数据结构的一些例子是队列、堆栈和列表,而图和树是非线性的。
3.什么是数组?
一个存储在连续位置和内存中的类似类型的数据集合被称为数组。数组代表了最简单的数据结构形式,你可以在其索引号的帮助下随机访问数据元素。
4.什么是多维数组?
多维数组表示分布在一个以上的维度的数据结构。对于每一个存储点,多维数组中都有多个索引变量。二维数组是最常用的多维数组。
5.链接列表如何比数组更有效?
链接列表比数组更有效的方法有很多,具体如下
- 插入和删除--由于需要通过移动数组中的现有元素来为新元素创造空间,这可能是一个昂贵的过程。然而,链接列表只需要更新节点的下一个指针中的地址。
- 动态数据结构--关联列表不需要在创建时指定一个初始大小。这意味着它可以在运行时通过分配和删除内存来增长和缩小。这使得链接列表成为一个动态的数据结构,而不像数组那样必须指定大小。
- 没有内存浪费--当在一个数组中,一定大小的内存已经被声明,但它没有被完全利用,这导致了空间的浪费。然而,由于链接列表不需要任何这样的声明,它可以根据程序的要求进行增长或缩小。
6.堆栈的应用有哪些?
堆栈可用于检查表达式中的平衡括号,评估后缀表达式,反转字符串,以及处理infix到后缀的转换问题。
7.在内存中存储一个变量的过程是什么?
变量是根据所需的内存量存储在内存中的。以下是在内存中存储变量的步骤。
- 首先,人们必须分配所需的内存量。
- 然后,在使用的数据结构的基础上存储变量。为此,使用动态分配等概念可以获得更高的效率。有了它,你可以根据要求实时访问存储单元。
结论
现在你已经知道了JavaScript中一些常见的数据结构,你现在可以在自己的程序中尝试使用这些数据结构。