栈结构
栈(Stack)是一种常见的数据结构,遵循 后进先出(LIFO, Last In First Out) 的原则。最后入栈的元素最先出栈。
js
function Stack() {
//栈属性
this.items = [];
// 入栈
Stack.prototype.push = function(element) {
// 在数组的尾部压入元素
return this.items.push(element);
}
// 出栈
Stack.prototype.pop = function() {
// 从数组尾部删除元素,并返回栈顶元素
return this.items.pop();
}
// 查看栈顶元素
Stack.prototype.peek = function() {
// 从数组尾部查看元素
return this.items[this.items.length - 1];
}
// 判断栈是否为空
Stack.prototype.isEmty = function() {
// 判断数组是否为空
return this.items.length === 0;
}
// 查看栈长度
Stack.prototype.size = function() {
// 查看数组长度
return this.items.length;
}
// toString 方法
Stack.prototype.toString = function() {
let resultString = '';
// 遍历数组
for (let i = 0; i < this.items.length; i++) {
resultString += this.items[i] + ''
}
return resultString
}
}
队列结构
队列(Queue)是一种常见的数据结构,遵循 先进先出(FIFO, First In First Out) 的原则。最先入队的元素最先出队
js
// 队列封装
function Queue() {
// 属性
this.items = [];
// 入队
Queue.prototype.enqueue = function(element) {
// 在数组的尾部添加元素
return this.items.push(element);
}
// 出队
Queue.prototype.inqueue = function() {
// 从数组中删除队头元素,并返回队头元素
return this.items.shift();
}
// 查看队头元素
Queue.prototype.front = function() {
// 从数组中查看数组第一个元素
return this.items[0];
}
// 判断队列是否为空
Queue.prototype.isEmpty = function() {
// 判断数组是否为空
return this.items.length === 0;
}
//查看队列长度
Queue.prototype.size = function() {
// 查看数组长度
return this.items.length;
}
// toString 方法
Queue.prototype.toString = function() {
let resultString = '';
// 遍历数组
for (let i = 0; i < this.items.length; i++) {
resultString += this.items[i] + ''
}
return resultString
}
}
优先级队列结构
优先级队列(Priority Queue)是一种特殊的队列结构,其中每个元素都有一个优先级,优先级最高的元素最先出队。与普通队列的先进先出(FIFO)不同,优先级队列遵循优先级最高者先出的原则。
js
function PriorityQueue() {
// 元素包含两部分 element 数据 priority 优先级
function QueueElement(element, priority) {
this.element = element;
this.priority = priority;
}
// 封装属性
this.items = [];
// 实现插入方法
PriorityQueue.prototype.enqueue = function(element, priority) {
// 创建 QueueElement 对象
const queueElement = new queueElement(element, priority)
// 判断队列是否为空
if (this.items.length == 0) {
// 为空 直接将数据插入到数组中
this.items.push(queueElement)
} else {
let added = false;
for (let i = 0; i < this.items.length; i++) {
// 数字越小,优先级越高。
if(queueElement.priority < this.items[i].priority) {
// 在数组的第 i 位插入数据
this.items.splice(i, 0, queueElement);
added = true;
break;
}
}
if(!added) {
// 优先级在数组中最低直接插入
this.items.push(queueElement)
}
}
}
// 其他方法跟队列的一致
}
链表结构
链表(Linked List)是一种动态数据结构,由一系列节点(Node)组成,每个节点包含数据和指向下一个节点的指针。与数组不同,链表的元素在内存中不必连续存储,因此可以高效地进行插入和删除操作。
js
function LinkedList() {
//内部类: 节点类
function Node(data){
this.data = data;
this.next = null;
}
//属性
this.head = null;
this.length = 0;
// 向列表添加一个新的项
LinkedList.prototype.append = function(data) {}
// 向列表的特定位置插入一个新的项
LinkedList.prototype.insert = function(position, data) {}
// 获取对应位置的元素
LinkedList.prototype.get = function(position) {}
// 返回元素在列表中的索引,如果列表中没有该元素则返回 -1
LinkedList.prototype.indexOf = function(data) {}
// 修改某个位置的元素
LinkedList.prototype.update = function(position, newData) {}
// 从列表的特定位置移除一项
LinkedList.prototype.removeAt = function(position) {}
// 从列表中移除一项
LinkedList.prototype.remove = function(data) {}
// 如果链表中不包含任何元素,返回 true
LinkedList.prototype.isEmpty = function() {}
// 返回链表包含的元素个数
LinkedList.prototype.size = function() {}
// toString 方法
LinkedList.prototype.toString = function() {}
}
向列表添加一个新的项
js
LinkedList.prototype.append = function(data) {
const newNode = new Node(data);
// 判断是否为第一个节点
if (this.length == 0) {
// 将头部指向第一结点
this.head = newNode;
} else {
// 在链表中找到最后一个结点插入节点
let current = this.head
while (current.next) { // 下一个结点不为空
current = current.next
}
// 最后节点的 next 指向新的节点
current.next = newNode;
}
// 更新链表长度
this.length += 1;
}
向列表的特定位置插入一个新的
开头:
指定位置:
js
LinkedList.prototype.insert = function(position, data) {
// 对 position 进行越界判断
if (position < 0 || position > this.length) return false;
// 创建 newNode
const newNode = new Node(data);
// 判断插入的位置是否是第一个位置
if (position == 0) {
newNode.next = head;
this.head = newNode;
} else {
// 找到指定位置
let index = 0;
let current = this.head;
// 前一个节点
let previous = null;
while (index++ < position) {
previous = current;
current = current.next;
}
newNode.next = current;
previous.next = newNode;
}
// 更新长度
this.length += 1
return true;
}
获取对应位置的元素
js
LinkedList.prototype.get = function(position) {
// 对 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;
}
返回元素在列表中的索引,如果列表中没有该元素则返回 -1
js
LinkedList.prototype.indexOf = function(data) {
let current = this.head;
let index = 0;
while(current) {
if (current.data === data) {
return index;
}
index += 1;
current = current.next;
}
return -1;
}
修改某个位置的元素
js
LinkedList.prototype.update = function(position, newData) {
// 对 position 进行越界判断
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;
}
从列表的特定位置移除一项
js
LinkedList.prototype.removeAt = function(position) {
// 对 position 进行越界判断
if (position < 0 || position >= this.length) return null;
let current = this.head;
// 判断是否删除第一个节点
if (position === 0) {
this.head = this.head.next;
} else {
let index = 0;
let previous = null;
while (index++ < position) {
previous = current;
current = current.next;
}
// 前者指向后者的后者
previous.next = current.next;
// 更新长度
this.length -= 1;
return current.data;
}
}
其他方法
js
// 从列表中移除一项
LinkedList.prototype.remove = function(data) {
// 根据 data 获取位置
let position = this.indexOf(data);
//根据位置信息,删除节点
return this.removeAt(position);
}
// 如果链表中不包含任何元素,返回 true
LinkedList.prototype.isEmpty = function() {
return this.length === 0;
}
// 返回链表包含的元素个数
LinkedList.prototype.size = function() {
return this.length;
}
// toString 方法
LinkedList.prototype.toString = function() {
let listString = '';
let current = this.head;
//循环获取节点
while (current) {
listString += current.data + " ";
current = current.next;
}
return listString;
}
双向链表
双向链表(Doubly Linked List)是一种常见的数据结构,属于链表的变种。与单向链表不同,双向链表的每个节点不仅包含指向下一个节点的指针,还包含指向前一个节点的指针。这种设计使得双向链表可以在两个方向上遍历,提供了更高的灵活性。
js
function DoublyLinkedList() {
// 属性
this.head = null;
this.tail = null;
this.length = 0;
//内部类
function Node(data) {
this.data = data;
this.prev = null;
this.next = null;
}
// 向双向链表添加一个新的项
DoublyLinkedList.prototype.append = function(data) {}
// 向双向链表的特定位置插入一个新的项
DoublyLinkedList.prototype.insert = function(position, data) {}
// 获取对应位置的元素
DoublyLinkedList.prototype.get = function() {}
// 返回元素在列表中的索引,如果列表中没有该元素则返回 -1
DoublyLinkedList.prototype.indexOf = function(data) {}
// 修改某个位置的元素
DoublyLinkedList.prototype.update = function(position, newData) {}
// 从双向链表的特定位置移除一项
DoublyLinkedList.prototype.removeAt = function(position) {}
// 从双向链表移除一项
DoublyLinkedList.prototype.remove = function(data) {}
// 如果双向链表中不包含任何元素,返回 true
DoublyLinkedList.prototype.isEmpty = function() {}
// 返回双向链表包含的元素个数
DoublyLinkedList.prototype.size = function() {}
// 获取双向链表的第一个元素
DoublyLinkedList.prototype.getHead = function() {}
// 获取双向链表的最后一个元素
DoublyLinkedList.prototype.getTail = function() {}
// toString 方法
DoublyLinkedList.prototype.toString = function() {}
// 返回正向遍历的节点字符串形式
DoublyLinkedList.prototype.forwordString = function() {}
// 返回反向遍历的节点字符串形式
DoublyLinkedList.prototype.backwordString = function() {}
}
向双向链表添加一个新的项
js
DoublyLinkedList.prototype.append = function(data) {
const newNode = new Node(data);
// 判断是否添加的是第一个结点
if (this.length === 0) {
this.head = newNode;
} else {
// 找到链表的最后一个结点
let current = this.head;
while (current.next) {
current = current.next;
}
// 最后一个结点指向下一个结点
current.next = newNode;
// 新的结点指向上一个结点
newNode.prev = current;
}
// 更新指向最后一个结点
this.tail = newNode;
// 更新长度
this.length += 1;
}
向双向链表的特定位置插入一个新的项
js
DoublyLinkedList.prototype.insert = function(position, data) {
// 越界判断
if (position < 0 || position > this.length) return false;
// 判断原来链表是否为空
if (this.length == 0) {
this.head = newNode;
this.tail = newNode;
} else {
// 判断 position 是否为 0
if (position == 0) {
this.head.prev = newNode;
newNode.next = this.head;
this.head = newNode;
} else if (position == this.length) {
newNode.prev = this.tail;
this.tail.next = newNode;
this.tail = newNode;
} else {
let current = this.head;
let index = 0;
while (index++ < position) {
current = current.next;
}
newNode.next = current;
newNode.prev = current.prev;
current.prev.next = newNode;
current.prev = newNode;
}
this.length += 1;
return true;
}
}
获取对应位置的元素
js
DoublyLinkedList.prototype.get = function() {
// 越界判断
if (position < 0 || position >= this.length) return false;
let current = this.head;
let index = 0;
while (index++ < position) {
current = current.next;
}
return current.data;
}
返回元素在列表中的索引,如果列表中没有该元素则返回 -1
js
DoublyLinkedList.prototype.indexOf = function(data) {
let current = this.head;
let index = 0;
while (current) {
if (current.data == data) {
return index;
}
current = current.next;
index += 1;
}
return -1;
}
修改某个位置的元素
js
DoublyLinkedList.prototype.update = function(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;
}
返回正反向遍历的节点字符串形式
js
// 返回正向遍历的节点字符串形式
DoublyLinkedList.prototype.forwordString = function() {
let current = this.head;
let resultString = "";
while (current) {
resultString += current.data + " ";
current = current.next;
}
return resultString;
}
// 返回反向遍历的节点字符串形式
DoublyLinkedList.prototype.backwordString = function() {
let current = this.tail;
let resultString = "";
while (current) {
resultString += current.data + " ";
current = current.prev;
}
return resultString;
}
// toString 方法
DoublyLinkedList.prototype.toString = function() {
this.forwordString();
}
从双向链表的特定位置移除一项
js
DoublyLinkedList.prototype.removeAt = function(position) {
// 越界判断
if (position < 0 || position >= this.length) return null;
let current = this.head;
if (this.length == 1) {
// 只存在一个结点
this.head = null;
this.tail = null;
} else {
// postion 为 0
if (position == 0) {
this.head.next.prev = null;
this.head = this.head.next;
} else if (position == this.length - 1) {
// postion 为 this.length
current = this.tail;
this.tail.prev.next = null;
this.tail = this.tail.prev;
} else {
// postion 为 任意值
let index = 0;
while (index++ < position) {
current = current.next;
}
current.prev.next = current.next;
current.next.prev = current.prev;
}
}
this.length -= 1;
return current.data;
}
其他方法
js
// 从列表移除一项
DoublyLinkedList.prototype.remove = function(data) {
let postion = this.indexOf(data);
return this.removeAt(postion);
}
// 如果链表中不包含任何元素,返回 true
DoublyLinkedList.prototype.isEmpty = function() {
return this.length == 0;
}
// 返回链表包含的元素个数
DoublyLinkedList.prototype.size = function() {
return this.length;
}
// 获取链表的第一个元素
DoublyLinkedList.prototype.getHead = function() {
return this.head.data;
}
// 获取链表的最后一个元素
DoublyLinkedList.prototype.getTail = function() {
return this.tail.data;
}
二叉树
二叉树是指每个节点最多含有两个子树的树结构。
特点:
- 所有节点最多拥有两个子节点,即度不大于2
- 左子树的键值小于根的键值,右子树的键值大于根的键值。
每一个结点(除叶子结点外)都有左子树和右子树.
js
// 结点
var Node = function(key) {
this.key = key;
this.left = null;
this.right = null;
}
遍历二叉树
- 先序遍历 (根 => 左结点 => 右结点)
- 中序遍历 (左结点 => 根 => 右结点)
- 后序遍历 (左结点 => 右结点 => 根)
根据上图:
- 先序遍历:ADBEFGC
- 中序遍历:DBEAFGC
- 后序遍历:DEBGFCA
js
// 中序遍历
function(node) {
if (node !== null) { // 判断参数(左孩子、右孩子、根结点)的值是否不为空
//访问左子树
inOrderTraverseNode(node.left);
console.log(node.key)
inOrderTraverseNode(node.right);
}
//如果为空,程序回溯,将结点的值输出
}
// 前序遍历
function(node) {
if (node !== null) { // 判断参数(左孩子、右孩子、根结点)的值是否不为空
console.log(node.key)
//访问左子树
preOrderTraverseNode(node.left);
preOrderTraverseNode(node.right);
}
//如果为空,程序回溯,将结点的值输出
}
// 后序遍历
function(node) {
if (node !== null) { // 判断参数(左孩子、右孩子、根结点)的值是否不为空
//访问左子树
postOrderTraverseNode(node.left);
postOrderTraverseNode(node.right);
console.log(node.key);
}
//如果为空,程序回溯,将结点的值输出
}
结点插入二叉树
根据二叉树的特点:左子树的键值小于根的键值,右子树的键值大于根的键值
比如:687234501这串数字插入到二叉树中
js
// 插入到二叉树
var insertNode = function(node, newNode) {
if (newNode.key < node.key) { //左子树
if (node.left === null) { // 左孩子为空
node.left = newNode
} else {
// 再次与左孩子的左孩子的值进行比较
insertNode(node.left, newNode)
}
} else { //右子树
if (node.right === null) {
node.right = newNode
} else {
insertNode(node.right, newNode)
}
}
};
// 创建结点及判断该结点是否为根结点并插入到二叉树中
this.insert = function(key) {
var newNode = new Node(key)
if (root === null) { // 二叉树中根节点为空
root = newNode;
} else {
insertNode(root, newNode)
}
};
寻找二叉树的最大最小值
根据二叉树的特点:左子树的键值小于根的键值,右子树的键值大于根的键值
得:
- 最小值是二叉树最左边的叶子结点。
- 最大值是二叉树最右边的叶子结点。
js
// 最小值
function(node) {
if (node) {
while (node && node.left !== null) {
node = node.left
}
return node.key
}
return null
}
// 最大值
function(node) {
if (node) {
while (node && node.right !== null) {
node = node.right
}
return node.key
}
return null
}
寻找二叉树的特定值
根据二叉树的特点:左子树的键值小于根的键值,右子树的键值大于根的键值
得:
- 如果该值小于该结点则向左子树遍历。
- 如果该值大于该结点则向右子树遍历。
- 遍历到叶结点,还为找到该值,则该树没有改特定值。
js
var searchNode = function(node, key) {
if (node === null) {
return false;
}
if (key < node.key) {
return searchNode(node.left, key)
} else if (key > node.key) {
return searchNode(node.right, key)
} else {
return true;
}
}
删除结点并保持二叉树结构不变
- 情况一:删除的结点是叶子结点:直接删除。
- 情况二:该结点小于(大于)当前遍历结点的值,则继续遍历左子树(右子树)。
- 情况三:结点刚好等于当前遍历结点值,此时还有分四种情况进行讨论(有左右子树,没有左子树,没有右子树,没有左右子树)
- 没有左右子树情况下,直接删除(node =null return node),即第一种情况。
- 没有左子树 将 删除结点的右子树 赋值给 上层结点 的左子树或者右子树。
- 没有右子树 将 删除结点的左子树 赋值给 上层结点 的左子树或者右子树。
- 有左右子树
- (1)在该结点的右子树中查找到最小值结点
- (2)更新要删除结点的值为最小值
- (3)删除最小值结点
js
//找到最小值的结点
function(node) {
if (node) {
while (node && node.left !== null) {
node = node.left;
}
return node
}
return null
}
//删除结点
function(node, key) {
if (node === null) {
return null;
}
if (key < node.key) {
node.left = removeNode(node.left, key)
return node;
} else if (key > node.key) {
node.right = removeNode(node.right, key)
} else { //相等
//没有左右子树,直接node 赋值为 null 返回null通过程序的回溯将node.left 或 node.right 赋值为 null
if (node.left === null && node.right === null) {
node = null;
return node;
}
//没有左子树,将 删除结点的右子树 赋值给 上层结点 的左子树或者右子树
if (node.left === null) {
node = node.right;
return node;
}
//没有右子树,将 删除结点的左子树 赋值给 上层结点 的左子树或者右子树
if (node.right === null) {
node = node.left;
return node;
}
//结点包含左子树和右子树
//在该结点的右子树中查找到最小值结点
var aux = findMinNode(node.right);
//更新要删除结点的值为最小值
node.key = aux.key;
//删除最小值结点
node.right = removeNode(node.right, aux.key);
return node;
}
}