做者正处大三复习阶段,一方面要把只看过概念的 TypeScript
实践一下,一方面要巩固一下数据结构基础,最后一拍大腿,为何不用TypeScript
来实现一遍数据结构呢!前端
特别提醒: 本文篇幅较长,可分为小节浏览。 node
仓库地址为: DataStructure-Algorithm-TS,可在此处查看详细代码,若有问题,欢迎提 issue !后续会持续更新一些算法方面的知识。git
二叉树是一种典型的树状结构,二叉树是每一个节点最多有两个子树的树结构,一般被称为“左子树”与“右子树”。github
首先咱们定义二叉树节点的数据结构算法
如下相关代码在 BinaryTree.ts 中typescript
class TreeNode {
val: any //节点内容
left: TreeNode //左子树
right: TreeNode // 右子树
constructor(val:any) {
this.val = val
this.left = this.right = undefined
}
}
复制代码
接着咱们定义二叉树的数据结构数组
class Tree <T> {
root : TreeNode
constructor (data: T[]){
// 临时存储全部节点,方便寻找父子节点
let nodeList : TreeNode[] = []
// 顶节点
let root : TreeNode
for(let i = 0, len = data.length; i < len; i++) {
let node = new TreeNode(data[i]);
nodeList.push(node);
if (i > 0) {
// 计算节点所在层级的指数 即每一层都是 2^k-1 个 k为层数 n = k -1
let n : number = Math.floor(Math.sqrt(i+1))
// 当前层的启始值
let q = Math.pow(2, n) - 1 // 索引值 减一
// 记录上一层的起始点
let p = Math.pow(2, n-1) - 1 //索引值 减一
// 找到当前节点的父节点
let parent: TreeNode = nodeList[p + Math.floor((i - q) / 2)]
// 将当前节点和上一次节点作关联
if (parent.left !== undefined) {
parent.right = node
} else {
parent.left = node
}
}
}
this.root = nodeList.shift()
nodeList.length = 0
}
}
复制代码
遍历顺序 左子树 >> 节点 >> 右子树bash
// 在 Tree 类中
inOrderTraversal (root : TreeNode, array : T[]) : T[] {
if (root) {
this.inOrderTraversal(root.left, array)
array.push(root.val)
this.inOrderTraversal(root.right, array)
}
return array
}
复制代码
遍历顺序 节点 >> 左子树 >> 右子树数据结构
// 在 Tree 类中
public preOrderTraversal (root : TreeNode, array: T[]) : T[] {
if (root) {
array.push(root.val)
this.preOrderTraversal(root.left, array)
this.preOrderTraversal(root.right, array)
}
return array
}
复制代码
遍历顺序 左子树 >> 右子树 >> 节点数据结构和算法
// 在 Tree 类中
public postOrderTraversal (root: TreeNode, array: T[]) : T[] {
if (root) {
this.postOrderTraversal(root.left, array)
this.postOrderTraversal(root.right, array)
array.push(root.val)
}
return array
}
复制代码
// 在 Tree 类中
public treeDepth (root: TreeNode) : number {
// 一个二叉树的深度为 左子树深度和右子树深度的最大值 + 1
return (root === undefined || root.val === null) ? 0 : Math.max(this.treeDepth(root.left), this.treeDepth(root.right)) + 1
}
复制代码
// 在 Tree 类中
public minDepth (root: TreeNode) : number {
if (root === undefined || root.val === null) {
return 0
}
let left = this.minDepth(root.left)
let right = this.minDepth(root.right)
return (left && right) ? 1 + Math.min(left, right) : 1 + left + right
}
复制代码
// 判断二叉树是否为平衡二叉树
public isBalanced (root: TreeNode) : boolean {
if (!root || root.val === null) {
return true;
}
let left = this.isBalanced(root.left)
let right = this.isBalanced(root.right)
// 若是存在不平衡状况即都不平衡
if (left === false || right === false || Math.abs(this.treeDepth(this.root.left) - this.treeDepth(this.root.right)) > 1) {
return false
}
return true
}
复制代码
此部分代码在 Symmetry.ts
对称二叉树的要求:
import Tree, {TreeNode} from './BinaryTree'
/** * 判断是否为对称二叉树 * 对称二叉树条件为: * - 根节点相等 * - 左子树的右节点和右子树的左节点相同 * - 右子树的左节点和左子树的右节点相同 * * @param {Tree} tree */
function isSymmetry (tree: Tree<Number>) : Boolean {
return isSymmetryTree(tree.root.left, tree.root.right)
}
/** * * @param {TreeNode} node1 * @param {TreeNode} node2 */
function isSymmetryTree (node1 : TreeNode, node2: TreeNode ) : Boolean {
// 若是两个节点都不存在
if (!node1 && !node2) {
return true;
}
// 若是只有一个节点不存在
else if (!node1 || !node2) {
return false;
}
// 若是节点值不相同
else if (node1.val !== node2.val) {
return false;
}
//向下递归调用
return isSymmetryTree(node1.left, node2.right) && isSymmetryTree(node1.right, node2.left);
}
export default isSymmetry
复制代码
此部分代码在 Mirror.ts
镜像即二叉树全部的的左右节点交换位置
import {TreeNode} from './BinaryTree'
/** * 使一个二叉树变化为他的镜像 * 即交换左右节点位置 * * @param {TreeNode} root */
function Mirror (root: TreeNode) : void {
if (root) {
let temp = root.right;
root.right = root.left;
root.left = temp;
Mirror(root.right);
Mirror(root.left);
}
}
export default Mirror
复制代码
此部分代码在 Tree 类中
// 二叉树层次遍历
public levelTraversal (root: TreeNode) : number[][] | number[] {
if (!root) return []
// 使用 queue 来存储当前层级的节点
let result = [], queue = [root]
while (queue.length) {
let levelSize = queue.length
let currentLevel = []
while (levelSize--) {
let node = queue.shift()
currentLevel.push(node.val)
if (node.left && node.left.val !== null) queue.push(node.left)
if (node.right && node.right.val !== null) queue.push(node.right)
}
result.push(currentLevel)
}
return result
}
复制代码
此部分代码在 BuildTree.ts 中
/** * 根据前序遍历与中序遍历序列构造二叉树 * * @param {number[]} preorder 先序遍历序列 * @param {number[]} inorder 中序遍历序列 * @return {TreeNode} */
function buildTreeByPreAndIn (preorder: number[], inorder: number[]): TreeNode {
if (!preorder.length && !inorder.length) {
return null;
}
// 前序遍历的第一个节点即为二叉树根节点
let root = new TreeNode(preorder[0])
// 获取根节点在中序序列中的位置
let position = inorder.indexOf(root.val)
// 中序遍历中根节点以前的节点为左子树节点
let midLeft = inorder.slice(0, position)
// 中序遍历中根节点以后的节点为右子树节点
let midRight = inorder.slice(position+1)
// 前序序列中根节点以后与中序遍历左子树节点相同长度为左子树
let preLeft = preorder.slice(1, position + 1)
// 前序序列中的右子树
let preRight = preorder.slice(position + 1)
// 递归生成左子树和右子树
root.left = buildTreeByPreAndIn(preLeft, midLeft)
root.right = buildTreeByPreAndIn(preRight, midRight)
return root
}
复制代码
function buildTreeByPostAndIn (postOrder: number[], inOrder: number[]) : TreeNode {
if (!postOrder.length && !inOrder.length) {
return null
}
// 后序遍历的最后一个节点为根节点
let root = new TreeNode(postOrder[postOrder.length - 1])
// 获取根节点在中序遍历中的位置
let position = inOrder.indexOf(root.val)
// 中序序列根节点以前的均为左子树
let midLeft = inOrder.slice(0, position)
// 中序序列根节点以后的均为右子树
let midRight = inOrder.slice(position+1)
// 后序序列从前开始的左子树长度与中序序列相同
let postLeft = postOrder.slice(0, position)
// 后序序列的右子树
let postRight = postOrder.slice(position, postOrder.length - 1)
root.left = buildTreeByPostAndIn(postLeft, midLeft)
root.right = buildTreeByPostAndIn(postRight, midRight)
return root
}
复制代码
此部分代码在 RouteSum.ts 中
/** * 计算是否有一条路径上的总和等于目标和 * @param {TreeNode} root * @param {number} sum * @return {boolean} */
function RouteSum (root : TreeNode, sum : number) : boolean {
const getSum = (root: TreeNode, sum: number, total: number) : boolean => {
// 判断是否为叶子节点,如果叶子节点计算当前路径上的和
if (!root.left && !root.right) {
total += root.val
if (total === sum) {
return true
} else {
return false
}
} else {
// 若是只存在左子树
if (root.left && !root.right) {
return getSum(root.left, sum, total+root.val)
}
// 若是只存在右子树
else if (root.right && !root.left) {
return getSum(root.right, sum, total+root.val)
}
else {
return (getSum(root.left, sum, total+root.val) || getSum(root.right, sum, total+root.val))
}
}
}
// 若是传入的是空树
if (!root || root.val === undefined) {
return false
}
return getSum(root, sum, 0);
}
复制代码
此部分代码在 RouteSum.ts 中
/** * 给定一个二叉树和一个目标和,找到全部从根节点到叶子节点路径总和等于给定目标和的路径。 * * @param {TreeNode} root * @param {number} sum * @return {number[][]} */
function RouteSumWithRoute(root: TreeNode, sum: number) : number[][] {
let result = []
const getSumRoute = (root: TreeNode, sum: number,total: number, array: number[] = []) => {
// 判断是否为叶子节点,如果叶子节点计算当前路径上的和
if (!root.left && !root.right) {
total += root.val
if (total === sum) {
array.push(root.val)
result.push(array)
}
} else {
array.push(root.val)
// 若是只存在左子树
if (root.left && !root.right) {
getSumRoute(root.left, sum, total+root.val, [...array])
}
// 若是只存在右子树
else if (root.right && !root.left) {
getSumRoute(root.right, sum, total+root.val, [...array])
}
else {
getSumRoute(root.left, sum, total+root.val, [...array])
getSumRoute(root.right, sum, total+root.val, [...array])
}
}
}
// 若是传入的是空树
if (!root) {
return []
}
getSumRoute(root, sum, 0)
return result
}
复制代码
此部分代码在 TreeToList.ts中
/** * 给定一个二叉树,原地将它展开为链表。 * @param {TreeNode} root */
function TreeToList(root: TreeNode) : void {
while (root != null) {
// 若是左子树为 null ,直接考虑右子树
if (root.left === null) {
root = root.right
} else {
// 寻找左子树最右的节点
let pre = root.left
while (pre.right !== null) {
pre = pre.right
}
// 将原来的右子树插入左子树最右边节点的右子树
pre.right = root.right
// 将左子树插入到右子树
root.right = root.left
root.left = null
root = root.right
}
}
}
复制代码
链表是用一组任意存储的单元来存储线性表的数据元素。一个对象存储着自己的值和下一个元素的地址。
相似于 React 16 的 Fiber Node 链接而成的 Fiber Tree ,就是个单链表结构。
此部分代码在 LinkList.ts 中
首先咱们定义链表内节点的数据结构
class ListNode {
val: any
next: ListNode
/** * * @param {*} val * @param {ListNode} next */
constructor (val: any, next: ListNode) {
this.val = val
this.next = next
}
}
复制代码
以后咱们定义链表的数据结构和一些基础操做(查询,插入,删除)
class List {
head: ListNode
constructor(arr ? : any[]) {
if (arr) {
this.head = new ListNode(arr[0], null)
let current = this.head
for(let i = 1; i < arr.length; i++) {
current.next = new ListNode(arr[i], null)
current = current.next
}
} else {
this.head = new ListNode(null, null)
}
}
/** * 从 0 开始计算,找到包括head 头节点 在哪的位于 index 位置的节点 * @param { Number } index * @return {ListNode} */
public find(index: number): ListNode {
let current = this.head;
for (let i = 0; i < index; i++) {
current = current.next;
}
return current;
}
/** * 在指定位置插入节点 * * @param {any} value * @param {number} index */
public insert(value: any, index: number): void {
// 获取当前位置前一个节点
let prev = this.find(index-1)
let next = new ListNode(value, prev.next)
prev.next = next
}
/** * 删除指定位置的节点 * * @param {number} index */
public delete(index: number): void {
// 若是要删除头节点
if (index === 0) {
this.head = this.head.next
} else if (this.find(index) === null || this.find(index).val === null) {
throw Error('输入节点不存在')
} else {
// 获取要删除节点的前一个节点
let prev = this.find(index-1)
prev.next = prev.next.next
}
}
}
复制代码
此部分代码在 List 类中
给定一个排序链表,删除全部重复的元素,使得每一个元素只出现一次。
/** * 删除重复元素 */
public DeleteDuplicates(): void {
let current = this.head
// 暂存值前一个节点的值
let temp: any
// 要删除节点的
let toDelete: ListNode
while(current && current.next !== null) {
temp = current.val
// 若是重复, 删除重复节点
if (current.next.val === temp) {
toDelete = current.next
current.next = toDelete.next
} else {
current = current.next
temp = current.val
}
}
}
复制代码
此部分代码在 List 类中
/** * 正序遍历链表,返回一个数组 * * @return {any[]} * */
public PositiveTraverse(): any[] {
let arr = []
let current = this.head
while (current !== null) {
arr.push(current.val)
current = current.next
}
return arr
}
/** * 逆序遍历链表,返回一个数组 * * @return {any[]} */
public NegativeTraverse(): any[] {
let arr = []
let current = this.head;
while (current !== null) {
arr.unshift(current.val)
current = current.next
}
return arr
}
复制代码
此部分代码在 ReverseList.ts 中
将链表进行翻转,尾节点变为新的头节点,输入一个链表的头节点,返回反转后的新的头节点
/** * 反转链表 * * @param {ListNode} head * @return {ListNode} */
function ReverseList (head: ListNode): ListNode {
let headNode = head
let preNode = null
let next = null
// 遍历全部的节点
while(headNode) {
// 暂存原来的next 用于遍历
next = headNode.next
// 将节点的next指向反转
headNode.next = preNode
preNode = headNode
headNode = next
}
return preNode
}
复制代码
此部分代码在 ReverseList.ts 中
反转从位置 m 到 n 的链表。
/** * 反转从位置 m 到 n 的链表 * @param {ListNode} head * @param {number} m 反转开始位置 * @param {number} n 反转结束位置 * @return {ListNode} */
function ReverseBetween (head: ListNode, m: number, n: number): ListNode {
if (!head || head.next == null) {
return head
}
let current = head
let prev = null
// 首先向后遍历使 current 为要反转的首个节点
while ( m > 1) {
prev = current
current = current.next
m--
n--
}
// 保存要反转节点的前一个节点
let prevTail = prev
// 保存第 m 个节点,即反转后的尾节点
let theTail = current
// 保存next节点用于遍历
let theNext = null
while (n > 0) {
theNext = current.next
current.next = prev
prev = current
current = theNext
n--
}
// 若是从首个节点开始反转
if (prevTail === null) {
head = prev
}
// 不然反转前一个节点链接反转后的头节点
else {
prevTail.next = prev
}
// 链接反转尾节点与原链表后序节点
theTail.next = current
return head
}
复制代码
此部分代码在 MergeList.ts 中
传入两个有序链表的头节点,返回一个合并两个链表的有序链表
/** * 合并两个有序链表 * * @param {ListNode} pHead * @param {ListNode} qHead * @return {List} */
function MergeList(pHead: ListNode, qHead: ListNode): List {
let list = new List()
if (!pHead) {
list.head = qHead
return list
} else if (!qHead) {
list.head = pHead
return list
}
let node = list.head
// 当两个链表均为彻底合并时
while(pHead && qHead) {
if (pHead.val < qHead.val) {
node.next = pHead
pHead = pHead.next
} else {
node.next = qHead
qHead = qHead.next
}
node = node.next
}
// 连接未彻底合并链表的后序节点
if (!pHead) {
node.next = qHead
} else {
node.next = pHead
}
list.head = list.head.next
return list
}
复制代码
这部分代码在 DeleteN.ts 中
给定一个链表,删除链表的倒数第 n 个节点,而且返回链表的头结点。
/** * 删除链表的倒数第N个节点 * * @param {ListNode} head * @param {number} n * @return {ListNode} */
function DeleteNFromEnd (head: ListNode, n: number) :ListNode {
if (!head || n <= 0) {
return null
}
let node = new ListNode(0, null)
node.next = head
// 利用双指针法,首先领 a b 节点都指向头节点
let a: ListNode = node
let b: ListNode = node
let c: ListNode = null
// a提早移动 n 个节点
for(let i = 0; i < n; i++) {
a = a.next
// 若是链表长度不超过 n
if (!a) {
return null
}
}
// 向后遍历直到a到达尾部,此时b就为要删除的节点,c始终为b的前一个节点
while(a) {
c = b
b = b.next
a = a.next
}
// 删除b节点
c.next = c.next.next
return node.next
}
复制代码
此部分代码在 RotateRight.ts 中
给定一个链表,让每一个节点向右移动k个位置。
例如:
输入: 1->2->3->4->5->NULL, k = 2
输出: 4->5->1->2->3->NULL
解释:
向右旋转 1 步: 5->1->2->3->4->NULL
向右旋转 2 步: 4->5->1->2->3->NULL
复制代码
输入: 0->1->2->NULL, k = 4
输出: 2->0->1->NULL
解释:
向右旋转 1 步: 2->0->1->NULL
向右旋转 2 步: 1->2->0->NULL
向右旋转 3 步: 0->1->2->NULL
向右旋转 4 步: 2->0->1->NULL
复制代码
/** * 给定一个链表,旋转链表,将链表每一个节点向右移动 k 个位置,其中 k 是非负数。 * * @param {ListNode} head * @param {number} k * @return {ListNode} */
function RotateRight (head: ListNode, k: number):ListNode {
// 判断链表是否为空或只有一个节点
if (head === null || head.next === null) {
return head
}
// 定义n为链表长度
let n : number
// 定义一个节点用来遍历链表
let old_tail = head
// 首先获取链表内节点个数
for(n = 1; old_tail.next !== null; n++) {
old_tail = old_tail.next
}
// 造成闭环
old_tail.next = head
// 新的尾节点为 (n - k % n - 1) 个节点
// 新的头节点为 (n - k % n) 个节点
// 定义新的尾节点
let new_tail = head
for(let i = 0; i < (n - k % n - 1); i++) {
new_tail = new_tail.next
}
// 定义新的头节点
let new_head = new_tail.next;
// 断开新的尾节点与后面的链接
new_tail.next = null
return new_head
}
复制代码
此部分代码在 Exchange.ts 中
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是须要实际的进行节点交换。
/** * 递归两两交换相邻的节点 * * @param {ListNode} head * @return {ListNode} */
function Exchange(head: ListNode) : ListNode {
// 若是遍历到最后一个或最后为单数节点
if (head === null || head.next === null) {
return head
}
// 找到后面要交换的节点
let next = head.next
// head连接后面已经完成的子链
head.next = Exchange(next.next)
// next 连接head
next.next = head
return next
}
复制代码
此部分代码在 SeparateList.ts 中
给定一个链表和一个特定值 x,对链表进行分隔,使得全部小于 x 的节点都在大于或等于 x 的节点以前。
同时保留两个分区中每一个节点的初始相对位置。
/** * 给定一个链表和一个特定值 x,对链表进行分隔,使得全部小于 x 的节点都在大于或等于 x 的节点以前。 * * @param {ListNode} head * @param {number} x */
function SeparateList (head: ListNode, x: number) {
// 使用双指针法
let before_x = new ListNode(0, null)
let after_x = new ListNode(0, null)
let before_head = before_x
let after_head = after_x
// 遍历原链表,小于的放在 before 大于等于的放在 after
while(head) {
if (head.val < x) {
before_x.next = head
before_x = before_x.next
} else {
after_x.next = head
after_x = after_x.next
}
head = head.next
}
// 结束尾节点
after_x.next = null
// 合并原链表
before_x.next = after_head.next
return before_head.next
}
复制代码
栈是一种先入后出的数据结构。只能经过栈顶进行入栈和出栈操做。
此部分代码在 Stack.ts 中
class Stack<T> {
// 栈的存储空间
data: Array<T>
// 栈顶元素索引
top: number
constructor() {
this.data = []
this.top = 0
}
// 入栈
public push (item: any):void {
this.data.push(item)
this.top++
}
// 出栈
public pop (): T {
this.top--
return this.data.pop()
}
// 返回栈顶的元素
public peek (): T {
return this.data[this.top - 1]
}
// 判断栈是否为空
public isEmpty (): Boolean {
return this.top === 0
}
// 返回栈的大小
public size (): number {
return this.top
}
// 清空栈
public clear (): void {
delete this.data
this.top = 0
this.data = []
}
// 打印栈
public print (): void {
console.log(this.data.toString())
}
}
复制代码
此部分代码在 StackCheck.ts 中
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的全部数字均不相等。
例如 入栈顺序为 [1,2,3,4,5] 那么 [4,3,5,2,1]是能够为出栈序列的,但[4,3,5,1,2]却不是
/** * 输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的全部数字均不相等。 * * @param {Array} pushOrder * @param {Array} popOrder * @return {Boolean} */
function StackCheck<T> (pushOrder: Array<T>, popOrder: Array<T>): Boolean {
// 若是长度不一样,则直接证实不对应
if (pushOrder.length !== popOrder.length) {
return false
}
// 作一个辅助栈
const stack = new Stack()
// 设置入序指针和出序指针
let push = 0, pop = 0;
// 将入栈序列元素一次进入辅助栈
// 检查辅助栈顶元素和出栈序列栈顶元素是否一致:
// 元素一致,弹出辅助栈元素,出栈序列指针后移
// 不一致,则证实不匹配
while(pop < popOrder.length) {
for(; push < pushOrder.length && popOrder[pop] !== stack.peek(); push++) {
stack.push(pushOrder[push])
}
// 若是插入完毕但无匹配 则证实部位匹配的序列
if (popOrder[pop] !== stack.peek()) {
return false
}
// 若一致 则辅助栈弹出栈元素
stack.pop()
pop++
}
return true
}
复制代码
队列是一种先进先出的数据结构。经过队首出队列,经过队尾入队列。
此部分代码在 Queue.ts 中
class Queue<T> {
// 队列的存储空间
queue: Array<T>
constructor () {
this.queue = []
}
// 入队列
public push(item: T):void {
this.queue.push(item)
}
// 出队列
public pop(): T {
if (this.queue.length === 0) {
return null
}
return this.queue.shift()
}
// 返回队首元素
public peek(): T {
return this.queue[0]
}
// 队列是否为空
public isEmpty() : Boolean {
return this.queue.length === 0
}
}
复制代码
此部分代码在 CircleQueue.ts 中
循环队列是一种线性数据结构,其操做表现基于 FIFO(先进先出)原则而且队尾被链接在队首以后以造成一个循环。它也被称为“环形缓冲器”。
循环队列的一个好处是咱们能够利用这个队列以前用过的空间。在一个普通队列里,一旦一个队列满了,咱们就不能插入下一个元素,即便在队列前面仍有空间。可是使用循环队列,咱们能使用这些空间去存储新的值。
class CircleQueue<T> {
// 队列长度限制
k: number
// 头部指针
head: number
// 尾部指针
tail: number
// 队列长度
size: number
// 存储空间
data: Array<T>
constructor(k: number) {
this.k = k
this.head = -1
this.tail = -1
this.size = 0
this.data = new Array(k)
}
// 获取队首元素,若是队列为空,返回-1
public getFront(): T | number {
return this.size === 0 ? -1 : this.data[this.head]
}
// 获取队尾元素,若是队列为空,返回-1
public getRear(): T | number {
return this.size === 0 ? -1 : this.data[this.tail]
}
// 入循环队列
public enQueue(item: T): Boolean {
// 队列已满
if (this.size === this.k) {
return false
}
if (this.tail === this.head && this.tail === -1) {
this.head++
}
// 判断是否尾节点是不是队列最后一个节点,若是是,经过改变为第一个来实现循环
this.tail = this.tail === this.k -1 ? 0 : this.tail + 1
this.size++
this.data[this.tail] = item
return true
}
// 出循环队列
public deQueue () {
if (this.size === 0) {
return false
}
delete this.data[this.head]
// 头节点向后移动
this.head = this.head === this.k - 1 ? 0 : this.head + 1
this.size--
// 若是清空后为空
if (this.size === 0) {
this.head = -1
this.tail = -1
}
return true
}
// 检查队列是否为空
isEmpty (): Boolean {
return this.size === 0
}
// 检查循环队列是否已满
isFull () {
return this.size === this.k
}
}
复制代码
此部分代码在 CircularDeque.ts
设计一个双端循环队列,头部能够插入和删除,尾部能够插入和删除,同时能够循环,咱们能够直接使用限制长度的数组直接实现。
class CircularDeque <T> {
// 队列长度限制
k: number
// 队列长度
size: number
// 存储空间
data: Array<T>
constructor(k: number) {
this.k = k
this.size = 0
this.data = new Array()
}
// 将一个元素添加到双端队列头部
public insertFront(item: T): Boolean {
if (this.size === this.k) {
return false
} else {
this.size++
this.data.unshift(item)
return true
}
}
// 将一个元素添加到双端队列尾部
public insertLast(item: T): Boolean {
if (this.size === this.k) {
return false
} else {
this.size++
this.data.push(item)
return true
}
}
// 从头部删除一个元素
public deleteFront(): Boolean {
if (this.size === 0) {
return false
} else {
this.size--
this.data.shift()
return true
}
}
// 从尾部删除一个元素
public deleteLast(): Boolean {
if (this.size === 0) {
return false
} else {
this.size--
this.data.pop()
return true
}
}
// 从双端队列头部得到一个元素
public getFront() : T | number {
if (this.size === 0) {
return -1
} else {
return this.data[0]
}
}
// 从双端队列尾部获取一个元素
public getRear(): T | number {
if (this.size === 0) {
return -1
} else {
return this.data[this.size - 1]
}
}
// 检查双端队列是否为空
public isEmpty(): Boolean {
return this.size === 0 ? true : false
}
// 检查双端队列是否满了
public isFull(): Boolean {
return this.size === this.k ? true : false
}
}
复制代码
堆的底层实际上是一棵彻底二叉树。
堆有两种情形,最大堆
和最小堆
此部分代码在 Heap.ts 中
enum Type {
Min = 'min',
Max = 'max'
}
class Heap {
// 堆的类型
type: Type
value: number[]
constructor(type: Type) {
this.type = type
this.value = []
}
// 传入数组,建立一个堆
public create(numbers: number[]): void {
this.value = numbers
const length = this.value.length
// 从第一个非叶子节点开始调整结构
for(let i = Math.floor((length/2) -1); i>=0;i--) {
this.adjust(i, length)
}
}
// 调整堆的结构
public adjust(index: number, length:number): void {
const array = this.value
for(let i = 2 * index + 1; i < length; i = 2 * i + 1) {
if (i + 1 < length) {
// 若是符合堆的规则
if ((this.type === Type.Max && array[i + 1] > array[i]) || (this.type === Type.Min && array[i+1] < array[i])) {
i++
}
}
// 若是不符合规则 则进行交换
if ((this.type === Type.Max && array[index] < array[i]) || (this.type === Type.Min && array[index] > array[i])) {
[array[index], array[i]] = [array[i], array[index]]
index = i
}
}
}
// 向堆中添加元素
public add (item: number): void {
const array = this.value
array.push(item)
if (array.length > 1) {
let index = array.length - 1
let target = Math.floor((index - 1) / 2)
while(target >= 0) {
if ((this.type === Type.Min && array[index] < array[target]) || (this.type === Type.Max && array[index] > array[target])) {
[array[index], array[target]] = [array[target], array[index]]
index = target
target = Math.floor((index - 1) / 2)
} else {
break
}
}
}
}
// 弹出堆顶元素
public pop(): number {
const array = this.value
let result = null
if (array.length > 1) {
result = array[0]
// 将最后一个叶子结点置于堆顶 以后调整结构
array[0] = array.pop()
this.adjust(0, array.length)
} else if (array.length === 1) {
return array.pop()
}
return result
}
}
复制代码
参考连接: