八大数据结构
前言
数据结构是一种具有一定的逻辑关系,在计算机中应用某种存储结构,并且封装了相应操作的数据元素集合。它包含三方面的内容,逻辑关系、存储关系以及操作。
常见的数据结构如下
栈
栈的描述
栈是一种运算受限的线性表,限定仅在表尾插入和删除操作,是一种先入后出的数据结构
栈的实现
想要用js实现栈的数据结构,需要保证实现栈具备以下的基础功能
- push 新增元素
- pop 删除栈顶元素
- peek 返回栈顶的元素
- clear清空栈
- size 栈的大小,也就是元素的个数
- isEmpty栈是否为空
js实现栈通常有两种方式
1、用数组模拟实现
// 用数组来模拟创建栈
const stack = [];
// 入栈
stack.push(0);
// 出栈
stack.pop();
// 返回栈顶元素
const peekValue = stack.pop();
// 清空栈
stack.length = 0;
// 获取栈的大小,判断栈是否为空
stack.length
2、用class实现
class Stack {
constructor () {
this.items = [];
}
// 新增
push(el) {
this.items.push(el);
}
// 删除栈顶元素
pop () {
this.items.pop();
}
......
}
队列
对列的描述
队列和栈类似,也是一种特殊的线性表,和栈不同的是,队列值允许在表的一端进行插入操作,另外一端进行删除操作。 如图:队列的存储结构
队列的实现
队列的常用方法
- enqueue(element):将元素添加到队列的末尾。
- dequeue():移除并返回队列的第一个元素。
- front():返回队列的第一个元素,但不对队列进行修改。
- isEmpty():检查队列是否为空,如果为空则返回true,否则返回false。
- size():返回队列中元素的个数。
- clear():清空队列中的所有元素。 同样可以用js 模拟或者
class Queue {
constructor() {
this.items = [];
}
// 入队
enqueue(element) {
this.items.push(element);
}
// 出队
dequeue() {
if (this.isEmpty()) {
return "队列已空";
}
return this.items.shift();
}
// 查看队头元素
front() {
if (this.isEmpty()) {
return "队列已空";
}
return this.items[0];
}
// 检查队列是否为空
isEmpty() {
return this.items.length === 0;
}
// 获取队列长度
size() {
return this.items.length;
}
// 清空队列
clear() {
this.items = [];
}
// 打印队列元素
print() {
console.log(this.items.toString());
}
}
// 使用示例
const queue = new Queue();
queue.enqueue(1);
queue.enqueue(2);
queue.enqueue(3);
console.log(queue.front()); // 输出 1
console.log(queue.size()); // 输出 3
console.log(queue.isEmpty()); // 输出 false
queue.dequeue();
queue.print(); // 输出 2, 3
queue.clear();
console.log(queue.isEmpty()); // 输出 true
数组
数组的描述
数组是一种聚合数据类型,它是将具有相同类型的若干变量有序地组织在一起的集合。
在js中,数组又有三种数组类型
1、普通数组,构造函数为Array
2、类数组(Array-like)是一种类似于数组的对象, 具有索引和length属性,例如 arguments对象,document.querySelectorAll查询到的dom集合,
3、类型化数组(Typed-Array),是Javascript中一种特殊的数组对象,它允许直接操作二进制数据,基于底层Array Buffer对象构建
链表
链表的描述
链表是一种数据元素按照链式存储结构进行存储的数据结构,这种存储结构具有在物理上存在非连续的特点。
链表分为,单链表、双链表、循环链表
链表的实现
// 定义节点类
class Node {
constructor(value) {
this.value = value;
this.next = null;
}
}
// 定义链表类
class LinkedList {
constructor() {
this.head = null;
this.tail = null;
this.length = 0;
}
// 在链表尾部添加新节点
append(value) {
const newNode = new Node(value);
if (this.head === null) {
this.head = newNode;
this.tail = newNode;
} else {
this.tail.next = newNode;
this.tail = newNode;
}
this.length++;
}
// 在链表指定位置插入新节点
insert(position, value) {
if (position < 0 || position > this.length) {
return false;
}
const newNode = new Node(value);
if (position === 0) {
newNode.next = this.head;
this.head = newNode;
if (this.length === 0) {
this.tail = newNode;
}
} else if (position === this.length) {
this.tail.next = newNode;
this.tail = newNode;
} else {
let current = this.head;
let previous = null;
let index = 0;
while (index < position) {
previous = current;
current = current.next;
index++;
}
previous.next = newNode;
newNode.next = current;
}
this.length++;
return true;
}
// 从链表中移除指定位置的节点
removeAt(position) {
if (position < 0 || position >= this.length) {
return null;
}
let current = this.head;
if (position === 0) {
this.head = current.next;
if (this.length === 1) {
this.tail = null;
}
} else {
let previous = null;
let index = 0;
while (index < position) {
previous = current;
current = current.next;
index++;
}
previous.next = current.next;
if (position === this.length - 1) {
this.tail = previous;
}
}
this.length--;
return current.value;
}
// 返回链表中指定位置的节点值
get(position) {
if (position < 0 || position >= this.length) {
return null;
}
let current = this.head;
let index = 0;
while (index < position) {
current = current.next;
index++;
}
return current.value;
}
// 返回链表中指定值的第一个节点位置
indexOf(value) {
let current = this.head;
let index = 0;
while (current) {
if (current.value === value) {
return index;
}
current = current.next;
index++;
}
return -1;
}
// 返回链表是否为空
isEmpty() {
return this.length === 0;
}
// 返回链表的长度
size() {
return this.length;
}
// 清空链表
clear() {
this.head = null;
this.tail = null;
this.length = 0;
}
// 将链表转换为数组
toArray() {
const result = [];
let current = this.head;
while (current) {
result.push(current.value);
current = current.next;
}
return result;
}
// 将链表转换为字符串
toString() {
return this.toArray().toString();
}
}
树
树的描述
树结构是一种非线性存储结构,存储的是具有“一对多”关系的数据元素的集合 树的种类
- 二叉树,树的任意节点的子树 <= 2
- 满二叉树,叶子节点都在同一层,并且除了叶子节点之外所有的节点,都有两个子节点
- 完全二叉树,对于一颗完全二叉树,深度为d(d > 1),除了第d层,所有的节点构成满二叉树,且第d层所有的节点连续的紧密排列
- 哈夫曼树,也称为最优二叉树,是一种特殊的二叉树,带权路径最短的二叉树。
- 二叉查找树,(二叉搜索树、二叉排序树、BST),若任意节点的子树不空,则左子树小与根节点,若任意节点的右子树不为空,则右子树大于根节点的值
- AVL高度平衡树,任意节点子树的最大差别为1的二叉搜索树。增加或者删除可能需要通过一次或者多次树旋转来重新平衡这个树。
- 红黑树,红黑树是每个节点,都带有颜色的二叉查找树,特征1,节点是红色或者黑色,特征2,根节点是黑色,特征3,每个红色节点的两个字节点都是黑色,每个叶子到跟的所有路径不能有两个连续的红色节点。特征4,从任意节点到每个叶子路径都包含相同数目的黑色节点
- B树,一颗m阶B树是一颗平衡的m路搜索树,查找、插入、删除的操作性能都能保持在logn
- 字典树,tire树,是一种树形结构,根节点不包含字符,除根节点之外,每个节点值包含一个字符,从跟节点到某一节点,路径链接起来,为改节点对应的字符串,每个节点所有的子节点包含的字符都不相同
图
图是一种非线性的数据结构,由节点(顶点)和连接节点的边组成。图可以用来表示各种实际问题中的关系和连接。
图的基本组成部分包括:
- 节点(顶点):图中的每个元素称为节点或顶点。节点可以表示实体、对象或抽象概念。
- 边:节点之间的连接称为边。边可以是有向的(箭头指向一个方向)或无向的(没有箭头)。
- 权重:边可以带有权重,表示节点之间的关联程度或距离。
图可以分为有向图和无向图:
- 有向图:边有方向,表示从一个节点到另一个节点的单向连接。
- 无向图:边没有方向,表示节点之间的双向连接。
图的常见应用包括:
- 社交网络:用图来表示人与人之间的关系,例如朋友关系、关注关系等。
- 地图导航:用图来表示地点之间的道路或路径,以便进行路线规划和导航。
- 网络拓扑:用图来表示计算机网络中的设备和连接,以便进行网络管理和故障排查。
- 排课表:用图来表示学校的课程和学生之间的关系,以便进行排课和课程安排。
- 知识图谱:用图来表示知识之间的关联和层次结构,以便进行知识管理和推荐。
图的算法包括:
- 深度优先搜索(DFS):从一个节点开始,尽可能深地访问图的节点,直到无法继续深入为止。
- 广度优先搜索(BFS):从一个节点开始,逐层地访问图的节点,先访问离起始节点最近的节点。
- 最短路径算法:计算图中两个节点之间的最短路径,例如 Dijkstra 算法和 Floyd-Warshall 算法。
- 最小生成树算法:找到连接图中所有节点的最小成本的子图,例如 Prim 算法和 Kruskal 算法。
- 拓扑排序:对有向无环图进行排序,使得每个节点的前驱节点都排在它的后面。
- 强连通分量:将有向图分解为强连通分量,其中每个分量内的节点可以互相到达。
堆
堆(Heap)是一种特殊的树形数据结构,一般讨论的都是二叉堆,它满足以下两个条件:
- 堆是一个完全二叉树:除了最后一层,其他层的节点都是满的,最后一层的节点从左到右排列。
- 堆中每个节点的值都大于等于(或小于等于)其子节点的值:这个特性称为堆序性(Heap Property)。
根据堆序性的不同,堆可以分为两种类型:
- 最大堆(Max Heap):堆中每个节点的值都大于等于其子节点的值。也就是说,堆中的最大值位于根节点。
- 最小堆(Min Heap):堆中每个节点的值都小于等于其子节点的值。也就是说,堆中的最小值位于根节点。
堆的主要操作包括:
- 插入(Insertion):将一个新元素插入到堆中,保持堆序性。
- 删除根节点(Deletion):删除堆中的根节点,并重新调整堆,保持堆序性。
- 查找根节点(Find-Min/Max):返回堆中的根节点,即最小值或最大值。
- 修改节点(Heapify):修改堆中的某个节点的值,并重新调整堆,保持堆序性。
堆的应用包括:
- 堆排序(Heap Sort):利用堆的特性进行排序,时间复杂度为O(nlogn)。
- 优先队列(Priority Queue):利用堆的特性实现高效的插入和删除操作,用于处理具有优先级的任务。
- 哈夫曼编码(Huffman Coding):利用堆构建哈夫曼树,用于数据压缩。
- 最短路径算法(Dijkstra Algorithm):利用堆实现Dijkstra算法,用于求解最短路径问题。
散列表
散列表(Hash Table),也称为哈希表,是一种根据关键字直接访问数据的数据结构。它通过将关键字映射到一个固定大小的数组中来实现快速的查找、插入和删除操作。
散列表的核心思想是使用哈希函数(Hash Function)将关键字映射到数组的索引位置。哈希函数将关键字转换为一个整数,然后通过取余运算将其映射到数组中的某个位置。这个位置称为哈希值(Hash Value)或散列值(Hash Code)。
散列表的主要操作包括:
- 插入(Insertion):将一个键值对(Key-Value)插入到散列表中。首先通过哈希函数计算关键字的哈希值,然后将键值对存储在对应的数组位置中。如果发生哈希冲突(两个关键字映射到同一个位置),则需要解决冲突。
- 查找(Search):根据给定的关键字在散列表中查找对应的值。通过哈希函数计算关键字的哈希值,然后在对应的数组位置中查找值。如果发生哈希冲突,需要继续查找直到找到对应的值或者确定值不存在。
- 删除(Deletion):根据给定的关键字从散列表中删除对应的键值对。通过哈希函数计算关键字的哈希值,然后在对应的数组位置中删除键值对。如果发生哈希冲突,需要继续查找并删除对应的键值对。
解决哈希冲突的方法包括:
- 链地址法(Chaining):将哈希冲突的键值对存储在同一个位置的链表中。
- 开放地址法(Open Addressing):在发生哈希冲突时,通过一定的探测序列(如线性探测、二次探测等)在散列表中找到下一个可用的位置。
散列表的优点是能够实现快速的查找、插入和删除操作,平均时间复杂度为O(1)。然而,散列表的性能取决于哈希函数的设计和冲突解决方法的选择。较差的哈希函数或冲突解决方法可能导致较高的冲突率,从而降低散列表的性能。
散列表在实际应用中有广泛的应用,例如字典、数据库索引、缓存等。了解散列表的原理和操作对于高效地处理大量数据非常重要。
算法的度量衡-时间复杂度和空间复杂度
常见的时间空间复杂度有
- 常数时间复杂度(O(1)):无论输入规模大小,算法的执行时间都是固定的。
- 线性时间复杂度(O(n)):算法的执行时间与输入规模成线性关系。
- 对数时间复杂度(O(log n)):算法的执行时间与输入规模的对数成关系。
- 平方时间复杂度(O(n^2)):算法的执行时间与输入规模的平方成关系。
- 指数时间复杂度(O(2^n)):算法的执行时间与输入规模的指数成关系。