链表
链表有一系列节点组成,每个节点一般至少会包含两部分的信息:
(1)元素数据
(2)指向下一个元素的指针
链表的操作: 创建、插入、删除、输出
链表的优点: 不需要初始化容量,可以任意加减元素; 添加或者删除元素时只需要改变前后两个元素结点的指向,所以添加,删除很快;
缺点: 因为含有大量的指针域,占用空间较大; 查找元素需要遍历链表来查找,非常耗时。
适用场景: 数据量较小,需要频繁增加,删除操作的场景
实现代码如下:
class Node {
constructor(data) {
//数据
this.data = data;
//指向
this.next = null;
}
}
class LinkedList {
constructor() {
this.head = null;
this.tail = null;
this.length = 0;
}
// 添加
append(data) {
// 先把内容传到node这个类里面赋值 最后拿到的结果是一个对象{data:data,next:null}
let node = new Node(data);
// 判断head是否是第一次添加 如果是的话直接赋值
if (!this.head) {
this.head = node;
this.tail = node
} else {
// 如果不是 要改变指向next
this.tail.next = node
this.tail = node
}
// 每加一次让length +1
this.length += 1;
return this;
}
// 按位置删除
remove(position) {
let current = this.head
let index = 0;
let prev = null;
// 如果位置不存在
let message = '该数据不存在'
if (position < 0 || position > this.length - 1) {
return message;
}
// 如果删除的是第一个
if (position === 0) {
// 如果是第一个的话就让head的指向等于head的下一个指向
this.head = this.head.next
} else {
// 如果不是第一个 把上一个的指向指向要删除的那一项的下一个 也就是跳过被删除的这一项
while (index++ < position) {
// 保存上一个到prev
prev = current
// 保存被删除项
current = current.next
}
// 被删除的上一个元素的指向=被删除项的指向
prev.next = current.next
// 如果删除的是最后一项
if (position === this.length - 1) {
this.tail = prev
}
}
this.length -= 1;
return current.data;
}
// 修改
updata(position, newdata) {
let message = '该数据不存在'
if (position < 0 || position > this.length - 1) {
return message;
}
let current = this.head;
let index = 0;
// 找到当前要修改的元素
while (index++ < position) {
current = current.next
}
// 修改内容
current.data = newdata
return this;
}
// 查找
get(position) {
let message = '该数据不存在'
if (position < 0 || position > this.length - 1) {
return message;
}
let current = this.head;
let index = 0;
// 找到当前的元素
while (index++ < position) {
current = current.next
}
return current.data;
}
// 插入
insert(position, data) {
let node = new Node(data)
let index = 0;
let prev = null;
let current = this.head;
// 判断是否是添加到第一个
if (position === 0) {
// 新元素的指向=原本第一个元素
node.next = this.head
this.head = node
} else {
while (index++ < position) {
// 保存上一个元素
prev = current
// 保存当前元素
current = current.next
}
// 上一个元素的指向=新添加的元素
prev.next = node
// 新添加的元素的指向=当前元素
node.next = current
}
// 如果插入的位置是最后的一个
if (position === this.length) {
this.tail = node
}
this.length += 1;
return this;
}
// 转数组
toArray() {
let arr = [];
let current = this.head
while (current) {
arr.push(current.data)
current = current.next
}
return arr;
}
// 转字符串
toString() {
let str = '';
let current = this.head
while (current) {
str += current.data + ' '
current = current.next
}
return str;
}
// 长度
size() {
return this.length
}
// 判断是否为空
isEmpty() {
return this.length === 0
}
}
栈
栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。只可以从栈顶删除。
栈的特点:先进后出,后出先进
看下图:


function Stack() {
this.items = []
//添加
Stack.prototype.push = (data) => {
this.items.push(data)
}
//删除
Stack.prototype.pop = () => {
return this.items.pop()
}
//长度
Stack.prototype.size = () => {
return this.items.length
}
//是否为空
Stack.prototype.isEmpty = () => {
return this.items.length === 0
}
//转字符串
Stack.prototype.tostring = () => {
let str = '';
for (let i = 0; i < this.items.length; i++) {
str += this.items[i] + ' '
}
return str;
}
}
队列
队列跟栈很相似,不同的是队列的特性,所以删除方法不一样 使用场景:因为队列先进先出的特点,在多线程阻塞队列管理中非常适用。
队列特性:先进先出,后进后出
看下示意图: 大白话的意思就是 谁是最先进去的就先删掉谁,最后进去的就最后删除

function Queue() {
this.items = []
//添加
Queue.prototype.push = (data) => {
this.items.push(data)
}
//删除
Queue.prototype.shift = () => {
return this.items.shift()
}
//长度
Queue.prototype.size = () => {
return this.items.length
}
//是否为空
Queue.prototype.ispty = () => {
return this.items.length === 0
}
//转字符串
Queue.prototype.tostring = () => {
let str = '';
for (let i = 0; i < this.items.length; i++) {
str += this.items[i] + ' '
}
return str;
}
}
二叉树
好处:
1、可以快速找出最大值、最小值
2、中序遍历 二叉树每个节点有三个值:left(左边节点)key(本身)right(右边节点) 看下代码:
class Node {
constructor(key) {
this.key = key;
this.left = null;
this.right = null;
}
}
添加节点代码:
class BinarySearchTree {
constructor() {
this.root = null;
}
insert(key) {
let node = new Node(key);
// 判断是不是第一次添加
if (!this.root) {
this.root = node
} else {
this._insert(this.root, node)
}
}
_insert(node, newNode) {
// 判断父节点是否小于根节点 小于就放左边 大于||等于就放右边
if (node.key > newNode.key) {
// 判断左边有没有节点
if (!node.left) {
// 没有就赋值
node.left = newNode
} else {
// 有的话在调用判断方法继续判断
this._insert(node.left, newNode)
}
} else {
// 判断右边有没有节点
if (!node.right) {
// 没有就赋值
node.right = newNode
} else {
// 有的话在调用判断方法继续判断
this._insert(node.right, newNode)
}
}
}
}
最大值、最小值
// 最大值
max() {
let currNode = this.root
while (currNode.right) {
currNode = currNode.right
}
return currNode.key
}
// 最小值
min() {
let currNode = this.root
while (currNode.left) {
currNode = currNode.left
}
return currNode.key
}
查找
// 两个方法 建议用循环方法 因为递归方法调用方法自身次数过多会溢出、报错
search(key) {
// 循环判断
let currNode = this.root
while (currNode) {
// 判断根节点和key 如果大于在右边找 如果小于在左边找 等于就直接返回找到了
if (currNode.key > key) {
if (currNode.left) {
currNode = currNode.left
} else {
return false
}
} else if (currNode.key < key) {
if (currNode.right) {
currNode = currNode.right
} else {
return false
}
} else {
return currNode.key === key
}
}
// 递归判断调用
// return this._search(currNode, key)
}
·//递归查找方法
// _search(currNode, key) {
// 判断是否为空,为空直接返回false
// if (!currNode) {
// return false;
// }
// 判断是不是小于 小于的话调用自身方法从左边找
// if (currNode.key > key) {
// return this._search(currNode.left, key)
// } else if (currNode.key < key) {
// 判断是不是大于 大于的话调用自身方法从右边找
// return this._search(currNode.right, key)
// } else {
// 如果相等返回true
// return currNode.key === key
// }
// }
中序遍历
中序遍历就是按照节点上的key值以升序遍历BTS上的左右节点 // 排序
middleSort() {
let arr = []
let currNode = this.root
return this._middleSort(currNode, arr)
}
_middleSort(currNode, arr) {
if (currNode) {
this._middleSort(currNode.left, arr)
arr.push(currNode.key)
this._middleSort(currNode.right, arr)
}
return arr;
}
先序遍历
先序遍历是从根节点开始遍历,先遍历左节点在遍历右节点
middleSort() {
let arr = []
let currNode = this.root
return this._middleSort(currNode, arr)
}
_middleSort(currNode, arr) {
if (currNode) {
//中序、先序、后序代码最大的不同就是push的这句代码的位置
arr.push(currNode.key)
this._middleSort(currNode.left, arr)
this._middleSort(currNode.right, arr)
}
return arr;
}
后序遍历
后序遍历是先访问叶子节点,从左子树到右子树,再到根节点
middleSort() {
let arr = []
let currNode = this.root
return this._middleSort(currNode, arr)
}
_middleSort(currNode, arr) {
if (currNode) {
//中序、先序、后序代码最大的不同就是push的这句代码的位置
this._middleSort(currNode.left, arr)
this._middleSort(currNode.right, arr)
arr.push(currNode.key)
}
return arr;
}
删除
removeNode(key) {
let node = this.root
return this._removeNode(node, 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;
}
else {
// 第一种情况:一个叶子节点(没有子节点)
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;
}
// 第三种情况:有两个子节点
let aux = this.minNode(node.right);
node.key = aux.key;
node.right = this._removeNode(node.right, aux.key);
return node;
}
};
minNode(node) {
if (node === null) return null;
while (node && node.left !== null) {
node = node.left;
}
return node;
};

排序
常用的三个排序方法:冒泡排序、选择排序、快速排序
1. 冒泡排序(BubbleSort)
原理: 1.比较相邻的元素。如果第一个比第二个大,就交换他们两个。 2.对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。 3.针对所有的元素重复以上的步骤,除了最后一个。 4.持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。 5.冒泡排序比较稳定。

class ArrayList {
constructor() {
this.arr = []
}
//随机数组
random() {
for (let i = 0; i < 5; i++) {
this.arr.push(Math.ceil(Math.random() * 100))
}
return this.arr;
}
//替换位置方法
swap(n, m) {
let num = this.arr[m]
this.arr[m] = this.arr[n]
this.arr[n] = num
}
// 冒泡排序
BubbleSort() {
for (let j = this.arr.length; j >= 0; j--) {
for (let i = 0; i < j - 1; i++) {
if (this.arr[i] > this.arr[i + 1]) {
this.swap(i, i + 1)
}
}
}
return this.arr;
}
}
2.选择排序(SelectionSort)
原理:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。

代码:
class ArrayList {
constructor() {
this.arr = []
}
//随机数组
random() {
for (let i = 0; i < 5; i++) {
this.arr.push(Math.ceil(Math.random() * 100))
}
return this.arr;
}
//替换位置方法
swap(n, m) {
let num = this.arr[m]
this.arr[m] = this.arr[n]
this.arr[n] = num
}
// 选择排序
SelectionSort() {
for (let j = 0; j < this.arr.length; j++) {
let min = j;
for (let i = j + 1; i < this.arr.length; i++) {
if (this.arr[min] > this.arr[i]) {
this.swap(min, i)
}
}
}
return this.arr;
}
}
3.快速排序(Quicksort)
原理:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

class ArrayList {
constructor() {
this.arr = []
}
//随机数组
random() {
for (let i = 0; i < 5; i++) {
this.arr.push(Math.ceil(Math.random() * 100))
}
return this.arr;
}
// 快速排序
Quicksort() {
return this._quick(this.arr)
}
_quick(arr) {
//定义三个数据
let left = []
let center = []
let right = [];
//获取中间值下标
let num = Math.ceil(arr.length / 2)
//如果数组为空了,就返回arr
if (arr.length === 0) {
return arr;
}
//循环判断 小于num放在left,相等的放在center,大于num放在right
for (let i = 0; i < arr.length; i++) {
if (arr[i] < arr[num]) {
left.push(arr[i])
} else if (arr[i] > arr[num]) {
right.push(arr[i])
} else {
center.push(arr[i])
}
}
//递归调用
return [...this._quick(left), ...center, ...this._quick(right)]
}
}