Typescript 強化數據結構

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

反轉鏈表 II

此部分代碼在 ReverseList.ts 中

反轉從位置 mn 的鏈表。

/** * 反轉從位置 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
    
}
複製代碼

刪除鏈表的倒數第N個節點

這部分代碼在 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
    }
}

複製代碼

參考連接:

相關文章
相關標籤/搜索