做者正處大三複習階段,一方面要把只看過概念的 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
}
}
複製代碼
參考連接: