數據結構:邏輯結構和存儲結構
數據結構是組織數據的方式,例如樹,但是要注意數據結構有兩種形式:邏輯結構和存儲結構,這兩種結構在表示一種數據結構的時候不一定完全相同的,邏輯結構是我們分析數據結構和算法的主要形式,而存儲結構則是數據結構在內存中的存儲形式。
邏輯結構是數據結構的邏輯的表示,同時用於分析數據結構時的形象表示,以后的數據結構設計方式按按照邏輯結構的形式,一般來說,所有的數據結構大部分都是討論其邏輯結構,算法的操作按照邏輯結構的形式,算法的操作說明也是按照邏輯結構的形式。例如二叉堆的邏輯表示形式為樹,但是實現的時候可以使用數組,這里的樹就是邏輯形式,數組則是存儲結構。邏輯結構又分為線性結構和非線性結構,線性結構例如線性表(數組和鏈表),棧和隊列,非線性結構如樹和圖。
存儲結構是數據結構的實際實現時采取的結構形式,可采取與相應的邏輯結構不同的結構形式,不一定和邏輯結構相同,盡量以簡單方便有效率為主,例如不相交集邏輯表示為樹,實現的時候使用數組。但是算法的實現還是按照邏輯結構的形式,也就是即使使用數組表示一種數據結構,其操作算法也不一定和常規的數組操作相同。存儲結構又分為:順序結構(數組或順序表,普通二叉堆使用數組,圖可以使用二維數組)、鏈式結構(鏈表、棧和隊列)、索引結構(樹、堆和優先隊列)、哈希結構(哈希表、散列結構、不相交集的數組形式是一種散列結構)。
這里的介紹主要是為了明顯區分邏輯結構和存儲結構,邏輯結構是算法形式上的,存儲結構是編程語言上的,在算法理解上,邏輯結構是需要重點關注的,因為描述算法是按照邏輯結構的形式。
二叉樹的存儲結構
二叉樹通常采用鏈式存儲結構,存儲結點由數據域和指針域(指針域:左指針域和右指針域)組成,二叉樹的鏈式存儲結構也稱為二叉鏈表,對滿二叉樹和完全二叉樹可按層次進行順序存儲
二叉樹存儲方式
存儲的方式和圖一樣,有鏈表和數組兩種,用數組存訪問速度快,但插入、刪除節點操作就比較費時了。實際中更多的是用鏈來表示二叉樹
順序存儲結構
用一組連續的存儲單元依次自上而下,自左至右存儲完全二叉樹上的結點元素,即將二叉樹上編號為i的結點元素存儲在加上定義的一維數組中下標為i-1的分量中。“0”表示不存在此結點——廣度優先-層序遍歷。
這種順序存儲結構僅適用於完全二叉樹。因為,在最壞情況下,一個深度為k且只有k個結點的單支樹(樹中不存在度為2的結點)卻需要長度為2的n次方-1的一維數組。

通過上圖我們可以分析得到數組表示的完全二叉樹擁有以下幾個性質:
-
Left = index * 2 + 1,例如:根節點的下標為0,則左節點的值為下標array[0*2+1]=1
-
Right = index * 2 + 2,例如:根節點的下標為0,則右節點的值為下標array[0*2+2]=2
-
Parent(i) = floor((i-1)/2),i 的父節點下標,Math.ceil((index/2)-1(向上取整),其父節點下標為floor((i-1)/2)(向下取整)
-
序數 >= floor(N/2)都是葉子節點,例如:floor(9/2) = 4,則從下標4開始的值都為葉子節點
順序存儲結構JavaScript代碼演示
復習可以跳過此部分……
數組轉化二叉樹
/**
* @name 層序遍歷
* @description
* left = index * 2 + 1,例如:根節點的下標為0,則左節點的值為下標array[0*2+1]=1
* right = index * 2 + 2,例如:根節點的下標為0,則右節點的值為下標array[0*2+2]=2
* 序數 >= floor(N/2)都是葉子節點,例如:floor(9/2) = 4,則從下標4開始的值都為葉子節點
* index>0,index的結點的父節點為Math.ceil((index/2)-1,
* @param arr {number}
* @return {null|[]}
*/
buildTreeByLevelOrder (arr) {
let nodesList = this.createNodeList(arr)
let length = nodesList.length
if (length === 0) {
return null
}
// 確定每個結點的左右結點
for (let i = 0; i < length / 2 - 1; i++) {
if (2 * i + 1 < length) {
nodesList[i].leftChild = nodesList[2 * i + 1]
nodesList[i].parentNode = nodesList[Math.floor((i - 1) / 2)]
}
if (2 * i + 2 < length) {
nodesList[i].rightChild = nodesList[2 * i + 2]
nodesList[i].parentNode = nodesList[Math.floor((i - 1) / 2)]
}
}
// 判斷最后一個根結點:因為最后一個根結點可能沒有右結點,所以單獨拿出來處理
let lastIndex = Math.round(length / 2) - 1
// 左結點
nodesList[lastIndex].leftChild = nodesList[lastIndex * 2 + 1]
// 右結點,如果數組的長度為奇數才有右結點
if (length % 2 === 1) {
nodesList[lastIndex].rightChild = nodesList[lastIndex * 2 + 2]
}
this.tree = nodesList[0]
return nodesList
}
二叉樹層轉化為數組(層序遍歷)
/**
* @description 深度優先遞歸
* @param tree {Node}
* @return {Array}
*/
levelTraverse (tree) {
// 分層,每層放一個數組
let buckets = [[tree]]
let i = 0
// 遞歸,
function tempFunction (node, i) {
i++
let nodes = []
if (node.leftChild) {
nodes.push(node.leftChild)
}
if (node.rightChild) {
nodes.push(node.rightChild)
}
if (nodes.length) {
if (buckets[i] === undefined) {
buckets[i] = nodes
} else {
buckets[i].push(...nodes)
}
nodes.forEach(item => {
tempFunction(item, i)
})
}
}
tempFunction(tree, 0)
let backs = []
buckets.forEach(bucket=>{
backs.push(...bucket.map((node)=>node.data))
})
return backs
}
演示代碼,詳情查看https://github.com/zhoulujun/algorithm
搜索樹的鏈式存儲結構
二叉樹的結點由一個數據元素和分別指向其左右子樹的兩個分支構成,則表示二叉樹的鏈表中的結點至少包含三個域:數據域和左右指針域。有時,為了便於找到結點的雙親,還可在結點結構中增加一個指向其雙親結點的指針域。利用這兩種結構所得的二叉樹的存儲結構分別稱之為二叉鏈表和三叉鏈表。 在含有n個結點的二叉鏈表中有n+1個空鏈域,我們可以利用這些空鏈域存儲其他有用信息,從而得到另一種鏈式存儲結構---線索鏈表。
二叉搜索樹的節點通常包含4個域,數據元素,分別指向其左,右節點的指針和一個指向父節點的指針所構成,一般把這種存儲結構稱為三叉鏈表。
用代碼初始化一個二叉搜索樹的結點:
-
一個指向父親節點的指針 parent
-
一個指向左節點的指針 left
-
一個指向右節點的指針 right
-
一個數據元素,里面可以是一個key和value
class BinaryTreeNode {
constructor (key, value) {
this.parent = null;
this.left = null;
this.right = null;
this.key = key;
this.value = value;
}
}
鏈式存儲結構JavaScript代碼演示
數組轉換為二叉-先序遍歷構建二叉樹

/**
* @description 先序遍歷構建二叉樹
* @param arr
*/
buildBinaryTreeByPreOrder (arr) {
let root = new Node(arr.shift())
arr.forEach(data => {
this.insertNumber(data, root)
})
this.tree = root
return root
}
/**
* @description 插入節點
* @param data {Number} 節點值,暫時為數字
* @param tree {Node} 插入的樹
*/
insertNumber (data, tree = this.tree) {
let newNode = new Node(data)
debugger
if (!tree.data) {
tree.data = data
} else {
this.insertNode(tree, newNode)
}
}
/**
* @description 插入節點
* @param node {Node} 節點值,暫時為數字
* @param newNode {Node} 插入的樹
*/
insertNode (node, newNode) {
newNode.parentNode = node
if (newNode.data < node.data) {
if (node.leftChild === null) {
node.leftChild = newNode
} else {
this.insertNode(node.leftChild, newNode)
}
} else {
if (node.rightChild === null) {
node.rightChild = newNode
} else {
this.insertNode(node.rightChild, newNode)
}
}
}
二叉樹轉換為數組
/**
* @description 前序遍歷 =>1.訪問根節點; 2.訪問左子樹; 3.訪問右子樹
* @param node {Node} 遍歷的樹
*/
preOrderTraverse (node = this.tree) {
// 數組存儲數遍歷值
let backs = []
// 遞歸,
function tempFunction (node) {
if (node.data !== null) {
// 先取根結點
backs.push(node.data)
// 如果存在左結點,取左節點的值
node.leftChild && tempFunction(node.leftChild)
// 如果存在右結點,取右結點值
node.rightChild && tempFunction(node.rightChild)
}
}
tempFunction(node)
return backs
}
關於算法相關的詳細代碼,查看https://github.com/zhoulujun/algorithm
參考內容
慕課網視頻課程:http://www.imooc.com/learn/888
javascript/js實現 排序二叉樹數據結構 學習隨筆 https://www.cnblogs.com/caimuguodexiaohongmao/p/11123933.html
js 中二叉樹的深度遍歷與廣度遍歷(遞歸實現與非遞歸實現) https://www.jianshu.com/p/5e9ea25a1aae
二叉樹就是這么簡單 https://zhuanlan.zhihu.com/p/34887220
轉載本站文章《講透學爛二叉樹(四):二叉樹的存儲結構—建堆-搜索-排序》,
請注明出處:https://www.zhoulujun.cn/html/theory/algorithm/TreeGraph/8284.html
