数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效
栈(Stack)
-
栈( stack )又称堆栈,是一种后进先出的有序集合,其中一端为栈顶,另一端为栈底,添加元素(称为压栈/入栈或进栈)时,将新元素压入栈顶,删除元素(称为出栈或退栈)时,将栈底元素删除并返回被删除元素。
-
特点:先进后出,后进先出(LIFO)。
-
例子:一叠书、一叠盘子。
手动实现一个栈,包含以下方法
注意数组的末尾是栈顶
-
push(element(s)): 此方法将新添加的元素添加至堆栈的顶部
-
pop():此方法删除栈顶的元素,同时返回已删除的元素
-
peek(): 返回堆栈的顶部元素
-
isEmpty(): 判断堆栈是否为空,如果为空,返回True, 否则返回False。
-
clear(): 清空堆栈所有的元素。
-
size(): 此方法返回堆栈元素的数量,类似数组的长度。
-
toArray(): 以数组的形式返回堆栈的元素。
-
toString():以字符串的形式输出堆栈内容。
class Stack {
constructor() {
this.items = [];
}
push(element) {
this.items.push(element);
}
pop() {
return this.items.pop();
}
peek() {
return this.items[this.items.length - 1];
}
isEmpty() {
return this.items.length === 0;
}
clear() {
this.items = [];
}
size() {
return this.items.length;
}
toArray() {
return this.items.slice();
}
toString() {
return this.items.toString();
}
}
队列(Queue)
队列,又称为伫列(queue),是先进先出(FIFO, First-In-First-Out)的线性表。在具体应用中通常用链表或者数组来实现。队列只允许在后端(称为rear)进行插入操作,在前端(称为front)进行删除操作
队列是遵循FIFO先进先出原则的一组有序的项。队列在尾部添加新元素,并从顶部移除元素。最新添加的元素必须排在队列的末尾。
队列的操作方式和堆栈类似,唯一的区别在于队列只允许新数据在后端进行添加。
手动实现一个队列,包含以下方法
-
enqueue(element):向队列尾部添加一个新的项。 -
dequeue():移除队列的第一项,并返回被移除的元素。 -
front():返回队列中第一个元素 —— 最先被添加,也将是最先被移除的元素。队列不做任何变动 (不移除元素,只返回元素信息 —— 与Stack类的peek方法类似)。 -
tail():返回队列中的最后一个元素,队列不做任何变动。 -
isEmpty():如果栈没有任何元素就返回true,否则返回false。 -
size():返回队列包含的的元素个数,与数组的length属性类似。 -
print():打印队列中的元素。
/**
* 2. 实现一个队列
*/
class Queue {
constructor (){
this.items = []
}
// enqueue(element):向队列尾部添加一个新的项。
enqueue( element ){
this.items.push(element)
}
// dequeue():移除队列的第一项,并返回被移除的元素。
dequeue (){
return this.items.shift()
}
// front():返回队列中第一个元素 —— 最先被添加,也将是最先被移除的元素。队列不做任何变动 (不移除元素,只返回元素信息 —— 与 Stack 类的 peek 方法类似)。
front (){
return this.items[0]
}
// tail():返回队列中的最后一个元素,队列不做任何变动。
tail (){
return this.items[this.items.length]
}
// isEmpty():如果栈没有任何元素就返回 true,否则返回 false。
isEmpty (){
return this.items.length === 0
}
// size():返回队列包含的的元素个数,与数组的 length 属性类似。
size (){
return this.items.length
}
// print():打印队列中的元素。
print (){
console.log(this.items.toString())
}
}
字典(Dictionary)
字典是用来存储唯一值的一种数据结构,通常以[键, 值]对的形式来存储数据,也称为映射。 es6中Map类的实现,就是我们所说的字典。
手动实现一个字典
-
set(key, value): 向字典中添加新元素。
-
remove(key): 使用键值从字典中移除键值对应的数据值。
-
has(key): 如果某个键值存在于这个字典中,则返回true,否则返回false。
-
get(key): 通过键值查找特定的值并返回。
-
clear(): 将字典中的所有元素都移除。
-
size(): 返回字典中所包含的元素的数量。与数组的length属性类似。
-
keys(): 将字典中所包含的所有的键名以数组的形式返回。
-
values(): 将字典中所包含的所有值以数组的形式返回。
class Dictionary {
constructor() {
this.items = Object.create(null);
}
has(key) {
return this.items.hasOwnProperty(key);
}
set(key, value) {
this.items[key] = value;
}
get(key) {
return this.items[key];
}
remove(key) {
return Reflect.deleteProperty(this.items, key);
}
clear() {
this.items = {};
}
size() {
return Object.keys().length;
}
keys() {
return Object.keys();
}
values() {
return Object.values();
}
}
集合(Set)
集合是由一组无序且唯一(即不能重复)的项组成的。也可以将集合看成一个既没有重复元素,也没有顺序概念的数组。通常以[值, 值]对的形式来存储数据
手动实现一个集合
-
add(value): 向集合添加一个新的项。
-
remove(value): 从集合移除一个值。
-
has(value): 如果值在集合中,返回true,否则返回false。
-
clear(): 移除集合中的所有项。
-
size(): 返回集合所包含元素的数量。与数组的length属性相似。
-
values(): 返回一个包含集合中所有值的数组。 以es6的Set类的实现为基础:
class Set {
constructor() {
this.items = Object.create(null);
}
has(value) {
return this.items.hasOwnProperty(value);
}
set(value) {
this.items[value] = value;
}
get(value) {
return this.items[value];
}
remove(value) {
return Reflect.deleteProperty(this.items, value);
}
clear() {
this.items = {};
}
size() {
return Object.keys(this.items).length;
}
values() {
return Object.values(this.items);
}
}
散列表(HashTable)
HashTable类也叫HashMap类,是Dictionary类的一种散列表实现方式。
散列表:顾名思义也就是离散的或者零散,即不连贯的列表,也可以类比于离散数组。
散列算法的作用是尽可能的在数据结构中找到一个值。如果使用散列函数就知道值的具体位置,因此能快速检索到该值。散列函数的作用是给定一个键值,然后返回值在表中的地址。
class HashTable {
constructor(){
this.table = []
}
/**
* 散列函数
* @param {*} key 键名
*/
hashCode(key) {
let hash = 0;
for (let i = 0; i < key.length; i++) {
hash += key.charCodeAt(i);
}
return hash % 37;
}
/**
* 向散列表增加/更新一个新的项
* @param {*} key 添加的键名
* @param {*} value 添加的值
*/
put (key, value) {
let position = this.hashCode(key)
this.table[position] = value
}
/**
* 根据键值从散列表中移除值
* @param {*} key 移除的键名
* @return {Boolean} 是否成功移除
*/
remove (key) {
if ( !key ) return false
let position = this.hashCode(key)
this.table[position] = undefined
return true
}
/**
* 根据键值检索到特定的值
* @param {*} key 查找的键名
* @return {*} 查找的值
*/
get (key) {
let position = this.hashCode(key)
return this.table[position]
}
/**
* 打印散列表中已保存的值
* @return {*} 散列表的值
*/
print () {
return this.table
}
}
数组(Array)
就是我们js里面的数组,没什么可说的
- 元素的值按顺序放置,并通过从 0 到数组长度的索引访问;
- 数组是连续的内存块;
- 它们通常由相同类型的元素组成(这取决于编程语言);
- 元素的访问和添加速度很快;搜索和删除是在 O(1) 中完成的。
链表(Linked Lists)
链表存储有序的元素集合,但不同于数组,链表中的元素在内存中并不是连续放置的。每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(也称指针或链接)组成。
-
push(element):向链表尾部添加一个新元素。
-
insert(element, position):向链表的特定位置插入一个新元素。
-
getVal(index):返回链表中特定位置的元素。如果链表中不存在这样的元素,则返回undefined。
-
remove(position):从链表的特定位置移除一个元素。
-
isEmpty():如果链表中不包含任何元素,返回true,如果链表长度大于0则返回false。
-
size():返回链表包含的元素个数,与数组的length属性类似。
-
toString():返回表示整个链表的字符串。
// 结构
{val: xxx, next: {val: xxx, next: {val: xxx, next: {...}}}}
class Node {
constructor(val) {
this.val = val;
this.next = null;
}
}
class LinkNodeList {
constructor() {
this.head = null;
this.count = 0;
}
isEmpty() { return this.size() === 0; }
// 从尾部添加节点
// 如果想链表的尾部添加一个元素,那么首先需要通过上面实现的Node类来创建一个节点。如果当前的head还不存在,则说明当前的链表还是一个空,所以直接将head节点赋值即可。否则就找到链表的最后一个节点,将它的next指向新push的节点即可。并且将count增加
push(element) {
const node = new Node(element);
// 第一次没有节点 直接赋值
if (this.head === null) {
this.head = node;
} else {
// 当前节点
let current = this.head;
// 循环节点 如果current.next没有值就代表是最后一个节点,有值就替换current,依次往下找节点
while (current.next) {
current = current.next;
}
// 找到最后一个节点,把它的current.next赋值为新节点
current.next = node;
}
this.count++;
}
// 返回特定位置的元素
getPosition(index) {
if (index > 0 && index <= this.count) {
let node = this.head;
for (let i = 0; i < index; i++) {
node = node.next;
}
return node;
}
return undefined;
}
// 返回特定值的元素
getValue(val) {
if (!this.head) {
return undefined
}
let node = this.head
while (node) {
if (node.val === val) {
return node
} else {
node = node.next
}
}
}
// 返回头元素
getHead() {
return this.head
}
// 返回尾元素
getTail() {
if (count < 2) {
return this.head
}
let node = this.head
while (node.next) {
node = node.next
}
return node
}
// 向指定位置插入指定元素
insert(ele, index) {
if (index >= 0 && index <= this.count) {
const node = new Node(ele);
if (index === 0) {
const current = this.head;
node.next = current;
this.head = node;
} else {
const prev = this.getVal(index - 1);
node.next = prev.next;
prev.next = node;
}
this.count++;
return true;
}
return false;
}
// 移除指定位置的节点
remove(index) {
if (index >= 0 && index < this.count) {
let current = this.head;
// 移除第一项
if (index === 0) {
this.head = current.next;
} else {
const prev = this.getVal(index - 1);
current = prev.next;
prev.next = current.next;
}
this.count--;
return current.val;
}
return undefined;
}
}
树(Tree)
二叉树:是每个结点最多有两个子树的树结构。一个是左侧子节点,一个是右侧子节点。
二叉搜索树(BST)是二叉树的一种,但是它只允许你在左侧节点存储(比父节点)小的值,在右侧节点存储(比父节点)大(或者等于)的值。
-
insert(key): 向树中插入一个新的键。
-
search(key): 在树中查找一个键,如果节点存在返回true,如果不存在则返回false。
-
inOrderTraverse: 通过中序遍历方式遍历所有节点。
-
preOrderTraverse: 通过先序遍历方式遍历所有节点。
-
postOrderTraverse: 通过后序遍历方式遍历所有节点。
-
min: 返回树中最小的值/键。
-
max: 返回树中最大的值/键。
-
remove(key): 从树中移除某个键。
// 结构
{key: xxx, left: {key, left: {}, right: {}}, right: {key, {key, left: {}, right: {}}}}
// 创建一棵树
function BinarySearchTree () {
// 初始化根节点为null
let root = null
// 声明节点Node类,用于创建多个独立的节点
let Node = function (key) {
this.key = key
this.left = null
this.right = null
}
// 下面是methods
this.insert = function (key) {
// 首先根据传入的键生成节点node
let newNode = new Node(key)
// 插入之前判断当前树中是否有根节点
if (!root) {
root = node
} else {
// 根节点存在则判断插入逻辑,需要辅助函数 insertNode
insertNode(root, newNode)
}
}
// 插入逻辑实际上就是判断要插入的子节点和父节点之间的大小,从而决定是该放在左边还是右边
let insertNode = functicon (node, newNode) {
// 左侧节点只能存放比父节点小的值
if(newNode.key < node.key) {
// 当前节点的左侧节点为空则可以插入,否则递归调用 insertNode
!node.left ? node.left = newNode : insertNode(node.left, newNode)
} else {
// 当前节点的右侧节点只能存放大于等于父节点的值
!node.right ? node.right = newNode : insertNode(node.right, newNode)
}
}
}
中序遍历:左节点根右节点 前序遍历:根左节点右节点 后序遍历:左节点右节点根 一切都是以根为基础,根输出的顺序
// 声明节点Node类,用于创建多个独立的节点
const Node = function (key) {
this.key = key;
this.left = null;
this.right = null;
};
class BinarySearchTree {
constructor() {
// 初始化根节点为null
this.root = null;
}
// 下面是methods
insert(key) {
// 首先根据传入的键生成节点node
const newNode = new Node(key);
// 插入之前判断当前树中是否有根节点
if (!this.root) {
this.root = newNode;
} else {
// 根节点存在则判断插入逻辑,需要辅助函数 insertNode
this.insertNode(this.root, newNode);
}
}
// 插入逻辑实际上就是判断要插入的子节点和父节点之间的大小,从而决定是该放在左边还是右边
insertNode = function (node, newNode) {
// 左侧节点只能存放比父节点小的值
if (newNode.key < node.key) {
// 当前节点的左侧节点为空则可以插入,否则递归调用 insertNode
!node.left ? (node.left = newNode) : this.insertNode(node.left, newNode);
} else {
// 当前节点的右侧节点只能存放大于等于父节点的值
!node.right ? (node.right = newNode) : this.insertNode(node.right, newNode);
}
};
// 中序遍历
inOrderTraverse(callback) {
this.inOrderTraverseNode(this.root, callback);
}
// 中序遍历
inOrderTraverseNode(node, callback) {
if (node) {
this.inOrderTraverseNode(node.left, callback);
callback(node.key);
this.inOrderTraverseNode(node.right, callback);
}
}
// 先序遍历
preOrderTraverse(callback) {
this.preOrderTraverseNode(this.root, callback);
}
// 先序遍历
preOrderTraverseNode(node, callback) {
if (node) {
callback(node.key);
this.preOrderTraverseNode(node.left, callback);
this.preOrderTraverseNode(node.right, callback);
}
}
// 后序遍历
postOrderTraverse(callback) {
this.postOrderTraverseNode(this.root, callback);
}
// 后序遍历
postOrderTraverseNode(node, callback) {
if (node) {
this.postOrderTraverseNode(node.left, callback);
this.postOrderTraverseNode(node.right, callback);
callback(node.key);
}
}
// 最小值
min() {
let node = this.root;
if (node) {
while (node && node.left) {
node = node.left;
}
return node.key;
}
return null;
}
// 最大值
max() {
let node = this.root;
if (node) {
while (node && node.right) {
node = node.right;
}
return node.key;
}
return null;
}
// 搜索
search(key) {
return this.searchNode(this.root, key);
}
searchNode(node, key) {
if (node) {
if (key < node.key) {
return this.searchNode(node.left, key);
} else if (key > node.key) {
return this.searchNode(node.right, key);
}
return node;
}
return null;
}
remove(key) {
this.root = this.removeNode(this.root, key);
}
// 删除指定值辅助类
removeNode(node, key) {
if (node === null) {
return null;
}
if (key < node.key) {
node.left = this.removeNode(node.left, key);
return node;
} else if (key > node.key) {
node.right = this.removeNode(node.right, key);
return node;
}
// 第一种情况,删除节点没有子节点
if (node.left === null && node.right === null) {
node = null;
return node;
}
// 第二章情况,删除节点有一个子节点
if (node.left === null) {
node = node.right;
return node;
} else if (node.right === null) {
node = node.left;
return node;
}
// 第三种情况,删除节点有很多子节点
// 我们需要找到删除节点,右子节点的最小值,用最小节点去更新删除节点
const aux = this.findMinNode(node.right);
node.key = aux.key;
node.right = this.removeNode(node.right, aux.key);
return node;
}
// 查找节点的最小值节点
findMinNode(node) {
while (node && node.left) {
node = node.left;
}
return node;
}
}
const tree = new BinarySearchTree();
// 插入节点
tree.insert(11);
tree.insert(7);
tree.insert(15);
tree.insert(5);
tree.insert(3);
tree.insert(9);
tree.insert(8);
tree.insert(10);
tree.insert(13);
tree.insert(12);
tree.insert(14);
tree.insert(20);
tree.insert(18);
tree.insert(25);