一、栈
堆栈具有后进先出的特点
栈具有如下方法:
push:从最后插入元素
pop:弹出栈顶元素并返回
peek:返回栈顶元素
实现代码:
function Stack() {
this.storage = [];
this.count = -1;
this.length = this.count + 1;
this.push = function (value) {
this.count++;
this.length = this.count + 1;
this.storage[this.count] = value;
}
this.pop = function () {
if (this.count === -1) {
return undefined
}
const result = this.storage[this.count];
this.storage.splice(this.count,1)
this.count--;
this.length = this.count + 1;
return result
}
this.peek = function () {
if (this.count === -1) {
return undefined
}
return this.storage[this.count];
}
}
个人实现思路:
-
push方法:将栈中的内容存入一个数组中,创建一个count变量充当类似指针的作用,一直指向数组最后一个值的索引,在调用push方法时先+1再赋值。 -
pop方法:由于count变量是指向数组最后一个值的索引,先对count变量进行判断,确定栈是否为空,再使用splice方法删除count对应的数据,count--。 -
peek方法:就是pop方法但是不删除对应数据,count不--
二、集合
集合与数组类似但是没有索引也不能重复
集合具有以下方法:
- values:返回集合所有元素
- size:返回集合长度
- has:判断集合是否有某个值
- add:添加值
- remove:删除值
- interaction:合并两个集合
- union:取交集
- difference:取差集
- subset:判断是否子集
实现代码:
function MySet() {
const collection = []
const length = 0;
this.values = function () {
return collection
}
this.size = function () {
return length;
}
this.has = function (value) {
return collection.includes(value)
}
this.add = function (value) {
if (this.has(value)) {
return
} else {
collection.push(value)
}
}
this.remove = function (value) {
if (this.has(value)) {
const index = collection.indexOf(value)
collection.splice(index,1)
}
else {
return undefined
}
}
/* 合集 */
this.interaction = function (otherSet) {
var interactionSet = new MySet();
var firstSet = this.values();
var secondSet = otherSet.values();
firstSet.forEach(function (e) {
interactionSet.add(e);
});
secondSet.forEach(function (e) {
interactionSet.add(e);
});
return interactionSet;
}
/* 交集 */
this.union = function (otherSet) {
var unionSet = new MySet()
var firstSet = this.values();
firstSet.forEach(function (e) {
if (otherSet.has(e)) {
unionSet.add(e)
}
})
return unionSet
}
this.difference = function (otherSet) {
const unionSet = this.union(otherSet).values()
const interactionSet = this.interaction(otherSet)
unionSet.forEach(function (e) {
interactionSet.remove(e)
})
return interactionSet.values()
}
this.subset = function (otherSet) {
const firstSet = this.values()
const secondSet = otherSet.values()
if (firstSet.length > secondSet.length) {
return false
}
else {
return firstSet.every(item => secondSet.includes(item))
}
}
}
个人实现思路: 首先创建一个collection数组用来存数据,再用length记录集合的长度
- values:直接返回collection数组
- size:直接返回length
- has:使用数组的includes方法实现,返回布尔值
- add:首先使用has判断集合是否有这个值,没有就使用push方法传入,length别忘+1
- remove:也是使用has先判断,再用数组的indexof取得这个值的索引,splice删除,再length-1
- interaction:先创建一个合集的新的Set实例,再将要合并的两个集合分别使用foreach将他们的值一个一个传入,由于之前定义的集合的性质,他们重合的值并不会重复导入
- union:创建一个交集的Set实例,将两个判断交集的集合,其中传入的集合使用foreach一个一个取得值,另一个集合使用has判断,如果有就添加进交集的集合中
- difference:差集,先取得他们的合集再取得他们的交集,交集使用foreach取得值,在合集中使用之前定义的remove方法删除,可得差集
- subset:首先判断如果传入的集合的length还要小于其判断是其父集的length,那么直接false,然后再使用数组的every方法,如果每个值在其父集中使用has方法都为true,则返回true。
三、队列
队列具有先进先出的特性
队列具有如下方法:
- enqueue:添加值
- dequeue:删除值
- front:取得队首值
- isEmpty:判断是否为空
实现代码如下:
unction Queue() {
this.storage = [];
this.frontPointer = 0;
this.backPointer = 0;
this.size = 0;
this.enqueue = function (value) {
this.storage[this.backPointer] = value;
this.backPointer++;
this.size = this.backPointer - this.frontPointer
}
this.dequeue = function () {
if (this.frontPointer === this.backPointer) {
return undefined
}
this.storage.splice(this.frontPointer,1)
this.backPointer--;
this.size = this.backPointer - this.frontPointer
}
this.front = function () {
return this.storage[this.frontPointer];
}
this.isEmpty = function () {
if (this.frontPointer === this.backPointer) {
return true
}
else {
return false
}
}
}
个人实现思路: 首先创建storage数组存放数据,创建两个类似指针,一个指向队首,一个指向队尾,初始化队列的长度为size
- enqueue:队尾指针指向队尾索引,直接进行赋值,队尾指针+1,重新赋值size的数据
- dequeue:先判断size的值是否为0,及首尾指针是否相等,不相等的话直接splice,并且队尾指针--,队首指针不变一直指向队首,重新赋值size
- front:与dequeue差不多,只是只需要取得队首值就行
- isEmpty:判断size是否为0
四、单链表
单链表的信息有两个,一个是当前的值element,另一个是指向下一个节点的next,链表节点的存在是由其前后节点来确定的
单链表有如下方法:
- head:返回其头结点
- size:返回链表节点数
- add:添加节点
- remove:删除节点
- indexof:返回节点的索引
- elementAt:返回索引的节点值
- addAt:在指定索引添加节点
- removeAt:在指定索引删除节点
实现代码:
function Node(element) {
this.element = element;
this.next = null;
}
function LinkedList() {
var head = null;
var length = 0;
this.head = function () {
return head;
}
this.size = function () {
return length;
}
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.indexOf = function (element) {
var index = 0;
var currentNode = head
while (currentNode) {
if (currentNode.element == element) {
return index
}
currentNode = currentNode.next
index++;
}
return -1
}
this.elementAt = function (index) {
var count = 0
var currentNode = head
if (index >= length || index < 0) {
throw new RangeError('index out of bounds')
}
else {
while (count != index) {
count++;
currentNode = currentNode.next
}
return currentNode.element
}
}
this.addAt = function (index, element) {
const node = new Node(element)
var currentIndex = 0;
var currentNode = head;
var previousNode;
if (index >= length || index < 0) {
throw new RangeError('index out of bounds')
}
if (index == 0) {
node.next = currentNode
head = node
} else {
while (currentIndex < index) {
currentIndex++;
previousNode = currentNode
currentNode = currentNode.next
}
previousNode.next = node
node.next = currentNode
}
length++;
}
this.removeAt = function (index) {
var currentIndex = 0
var currentNode = head
var previousNode
if (index > length || index < 0) {
throw new RangeError('index out of bounds')
}
if (index == 0) {
head = currentNode.next
}
else {
while (currentIndex < index) {
currentIndex++;
previousNode = currentNode
currentNode = currentNode.next
}
previousNode.next = currentNode.next
}
length--;
}
}
个人实现思路: 首先创建一个node函数,其用于创建节点,在LinkedList中先初始化head节点为null,length为0
- head:返回head
- size:返回length
- add:首先判断如果head为null,那么赋值给head,否则从头结点开始往下遍历,如果遍历的当前节点的下一个节点为null,跳出遍历,将创建的node节点赋值给它
- remove:首先判断其传入的element是否与头节点符合,如果符合那么将头结点向其next节点传递,如果不符合,就一直向下遍历,直到找到与其符合的节点,那么这个节点的上一个节点的next属性指向当前节点的下一个节点。
- indexof:创建一个count变量,使用循环判断当前节点的值是否与传入的值,count++,符合便跳出循环返回count,不符合继续向下遍历
- elementAt:同样创建一个count变量,记录当前已经循环到了哪个索引,先进行判断传入的值是否合理,再利用循环,如果不符合的话count++并且当前节点指向下一个节点,符合便跳出循环返回count
- addAt:使用indexof一样的思路,遍历找到索引对应的节点,然后将其上一个节点的next指向新创建的节点,新创建的节点next指向当前节点
- removeAt:与addAt差不多,不同的是找到后其上一个节点的next指向下一个节点
五、哈希表
哈希表是键值数据结构,其通过hash函数将key的值转换成一个数字,然后将值传入到数字对应索引中。如果哈希表值与传入值冲突,1. 开放寻址法:当发生冲突时,通过一定的方法(如线性探测、二次探测、双重散列等)寻找另一个空槽位来存储冲突的键值对。2. 链地址法:将哈希表的每个槽位指向一个链表(或其他数据结构),当发生冲突时,将冲突的键值对添加到对应槽位的链表中。3. 再哈希法:通过另一个哈希函数再次对发生冲突的键进行哈希运算,得到另一个槽位来存储键值对。
哈希表有如下方法:
- add:添加值
- remove:删除值
- lookup:返回key对应的值
实现代码:
function hash(string, max) {
var hash = 0
for (let i = 0; i < string.length; i++) {
hash += string.charCodeAt(i);
}
return hash % max
}
function HashTable() {
let storage = [];
const storageLimit = 15;
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;
console.log('true');
inserted = true;
}
}
if (inserted === false) {
storage[index].push([key, value])
}
}
}
this.remove = function (key) {
var index = hash(key, storageLimit)
if (
storage[index] === undefined
) {
return false
}
else {
for (let i = 0; i < storage[index].length; i++) {
if (storage[index][i][0] === key) {
storage[index].splice(i, 1)
}
}
}
}
this.lookup = function (key) {
var index = hash(key, storageLimit)
if (storage[index] === undefined) {
return undefined
}
else {
for (let i = 0; i < storage[index].length; i++) {
if (storage[index][i][0] === key) {
return storage[index][i][1]
}
}
}
}
}
个人实现思路: 首先创建一个hash函数,传入参数为哈希表最大存储长度,以及hashTable存入数据的key,通过charCodeAt方法其他方法也行,将其变成一个对应的数字,并且返回这个数字。创建一个数组存数据。
- add:先利用hash函数得到索引,再判断对应索引是否为空,为空则直接push,不为空使用for循环对于数组中的数据进行判断,若数组对应的key与add传入的key吻合,则重新赋值,不吻合则直接push。
- remove:将传入的key通过hash函数转换为index,如果index对应的没值则返回false,有值就使用for循环找到对应index索引下的数组是否有值与传入的key相等,符合则splice
- lookup:与remove逻辑差不多,只是找到后返回其value不用删除
六、树
树的结构有很多如二叉树、红黑树、二叉平衡树,这里是二叉搜索树
树有如下方法:
- add:添加一个树节点
- findMin:找到节点值最小的节点
- findMax:找到节点值最大的节点
- find:找到对应节点值的节点
- isPresent:对应节点是否存在
- remove:删除节点
实现代码:
function Node(element) {
this.element = element;
this.left = null;
this.right = null;
this.parent = null;
}
function Tree() {
this.root = null;
this.add = function (element) {
var newNode = new Node(element);
if (this.root === null) {
this.root = newNode;
} else {
var currentNode = this.root;
while (true) {
if (element < currentNode.element) {
if (currentNode.left === null) {
currentNode.left = newNode;
newNode.parent = currentNode;
break;
} else {
currentNode = currentNode.left;
}
} else {
if (currentNode.right === null) {
currentNode.right = newNode;
newNode.parent = currentNode;
break;
} else {
currentNode = currentNode.right;
}
}
}
}
};
this.findMin = function () {
var currentNode = this.root;
if (currentNode === null) {
return undefined;
} else {
while (currentNode.left !== null) {
currentNode = currentNode.left;
}
return currentNode.element;
}
};
this.findMax = function () {
var currentNode = this.root;
if (currentNode === null) {
return undefined;
} else {
while (currentNode.right !== null) {
currentNode = currentNode.right;
}
return currentNode.element;
}
};
this.find = function (element) {
var currentNode = this.root;
if (currentNode === null) {
return undefined;
}
while (currentNode) {
if (currentNode.element === element) {
return currentNode
}
if (element < currentNode.element) {
currentNode = currentNode.left
} else {
currentNode = currentNode.right
}
}
}
this.isPresent = function (element) {
if (this.find(element)) {
return true
}
else {
return false
}
}
this.remove = function (element) {
var deleNode = this.find(element);
if (!deleNode) {
return undefined; // 如果找不到要删除的节点,则返回 undefined
}
// 定义一个辅助函数,用于获取节点的子节点中的最小节点
function findMinNode(node) {
while (node && node.left !== null) {
node = node.left;
}
return node;
}
// 情况1:要删除的节点没有子节点
if (deleNode.left === null && deleNode.right === null) {
if (deleNode === this.root) {
this.root = null; // 如果要删除的节点是根节点,则直接将根节点置空
} else {
if (deleNode.parent.left === deleNode) {
deleNode.parent.left = null; // 更新父节点的左子节点
} else {
deleNode.parent.right = null; // 更新父节点的右子节点
}
}
}
// 情况2:要删除的节点只有一个子节点
else if (deleNode.left === null) {
if (deleNode === this.root) {
deleNode.right.parent = null;
this.root = deleNode.right; // 如果要删除的节点是根节点,则将根节点指向其右子节点
} else {
if (deleNode.parent.left === deleNode) {
deleNode.parent.left = deleNode.right; // 更新父节点的左子节点
} else {
deleNode.parent.right = deleNode.right; // 更新父节点的右子节点
}
}
} else if (deleNode.right === null) {
if (deleNode === this.root) {
deleNode.left.parent = null;
this.root = deleNode.left; // 如果要删除的节点是根节点,则将根节点指向其左子节点
} else {
if (deleNode.parent.left === deleNode) {
deleNode.parent.left = deleNode.left; // 更新父节点的左子节点
} else {
deleNode.parent.right = deleNode.left; // 更新父节点的右子节点
}
}
}
// 情况3:要删除的节点有两个子节点
else {
var minRightNode = findMinNode(deleNode.right); // 找到右子树中的最小节点
deleNode.element = minRightNode.element; // 将右子树中的最小节点的值赋值给要删除的节点
this.remove(minRightNode.element); // 递归地删除右子树中的最小节点gogogo~ ~ ~
}
};
}
个人实现思路: 首先创建树节点函数,其中定义四个值,节点值element,左节点left,右节点right,父节点parent。树中先初始化一个根节点
- add:需要判断的情况很多,首先判断是否根节点为null,再判断如果不为null其值与当前节点的值进行比较,如果小于当前节点值就往其左子树遍历,如果大于当前值就往右子树遍历,直到遍历到其左节点或右节点为null,赋值后便break跳出循环
- findMin:由于左边节点是最小的,所以只需要往左子树循环遍历,找到最左边的节点,取得其值。
- findMax:则是取右子树,找到最右边的节点的值
- find:循环遍历树,如果传入的值与当前根节点值相等则返回根节点,如果不相等再根据其值与根节点值比较,小于根节点值就往左子树遍历,大于就往右指数遍历,每循环一次当前节点就往其左节点或右节点赋值。
- isPresent:就返回find的值就行
- remove:这个函数判断的情况很多,首先使用find函数找到element对应的节点,如果没用则返回undefined,如果有在判断三种情况,情况一,删除的节点没有子节点,那么再判断该删除的节点是否为根节点,如果不是就再判断其是其父节点的左节点还是右节点,根据不同情况将其父节点的left或right置null,情况二,要删除的节点有一个子节点,首先判断其子节点位于left还是right,再判断其为根节点还是其他节点,情况三,要删除的节点有两个节点,那么只要找到当前要删除的节点的右子树中的最小节点,找到后将其值赋值给要删除的节点,并使用remove方法递归将右子树中的最小节点删除就行,其实就是用右子树的最小节点替换当前要删除的节点。