目錄
一、二叉樹基礎概念
1.1 基礎概念:
- 子樹:非空二叉樹節點數N>1時候,除了根節點之外的結點可以分爲互不相交的有限集合,每個集合本身又是一顆二叉樹。
- 結點的度:結點擁有子樹的數量。
- 葉子結點,也稱爲終端結點:度爲0的結點
- 非終端結點,也稱爲分支結點:度不爲0的結點
- 滿二叉樹:深度爲k的二叉樹具有 2^k-1 個結點
- 完全二叉樹(堆):完全二叉樹是效率很高的數據結構。對於深度爲K的,有n個結點的二叉樹,當且僅當其每一個結點都與深度爲K的滿二叉樹中編號從1至n的結點一 一對應時稱之爲完全二叉樹。(堆即是完全二叉樹的數組對象)
結點 node,不是節點。
1.2 二叉樹的性質
- 性質一:二叉樹的第i層上至多有2^(i-1)個節點。
- 性質二:深度爲k的二叉樹,至多有 2^k-1 個節點
- 性質三:二叉樹若終端結點爲n0,度爲2的節點爲n2,則 n0=n2+1
前兩個容易證明,第三個性質,所有結點爲n,度爲1的結點個數爲n1,度爲2的結點個數n2
n=n0+n1+n2
設置B爲分支的個數,即兩個結點之間的連接情況,B=n1+2×n2,除了根結點之外,所有結點均有分支進入,n=B+1
結合起來即爲: n0=n2+1
- 性質四:n結點的完全二叉樹的深度爲 floor[ log2(n) ] +1
- 性質五:當前節點編號爲n,則其左孩子結點爲 2×n+1,右 2×n+2
二、二叉樹的遍歷
2.1 三種遍歷方法
前序,中序,後續是根據根節點出現的位置來命名的。
- 前序:根,左,右
- 中序:左,根,右
- 後續:左,右,根
寬度優先遍歷:
一層一層往下遍歷,10,6,14,4,8,12,16
2.2 二叉樹的構建
設某棵二叉樹的中序遍歷序列爲ABCD,前序遍歷序列爲CABD,則後序遍歷該二叉樹得到序列爲( )?
正確答案: A
- BADC
- BCDA
- CDAB
- CBDA
之前感覺有點難,因爲沒弄明白遍歷是怎麼進行的,弄明白遍歷的進行即可看懂。
2.3 重建方法及解析
輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建出該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重複的數字。例如輸入前序遍歷序列{1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6},則重建二叉樹並返回。做這種題需要有遞歸的思想,並不難。理論推導:
前序第一個是根節點的數值,必然是1,中序的時候左邊爲1左邊的值,右邊爲1右邊的值。 然後在分開的左子樹和右子樹之中重複進行此步驟。
剩下的步驟遞歸即可完成。
2.4 重建二叉樹的代碼實現
輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建出該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重複的數字。例如輸入前序遍歷序列{1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6},則重建二叉樹並返回。
c++重建二叉樹
關於這個,依然是真的vector的操作需要知道。尾部添加變量的函數是push_back.
/**
* Definition for binary tree
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
int length=pre.size();
if(length==0)return NULL;
//root node
TreeNode* root=new TreeNode(pre[0]);
vector<int> left_pre,left_vin,right_pre,right_vin;
//find root node in vin
int root_loc_vin;
for( root_loc_vin=0;root_loc_vin<length;root_loc_vin++){
if (vin[root_loc_vin]==pre[0])break;
}
//split vin,pre to left
for(int idx_left_vin=0;idx_left_vin<root_loc_vin;idx_left_vin++){
left_vin.push_back(vin[idx_left_vin]);
left_pre.push_back(pre[idx_left_vin+1]);
}
//split vin,pre to right
for(int idx_right_vin=root_loc_vin+1;idx_right_vin<length;idx_right_vin++){
right_vin.push_back(vin[idx_right_vin]);
right_pre.push_back(pre[idx_right_vin]);
}
root->left=reConstructBinaryTree(left_pre,left_vin);
root->right=reConstructBinaryTree(right_pre,right_vin);
return root;
}
};
有幾點需要注意:
指針類型的函數,如果直接返回,需要返回NULL,
創建新的節點的話需要用指針=new struct(構造函數)
vector的push_back()函數,複習前面的insert()函數,刪除用pop_back()函數
python重建二叉樹
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
# 返回構造的TreeNode根節點
def reConstructBinaryTree(self, pre, tin):
# write code here
if(len(pre)==0):
return None
root=TreeNode(pre[0])
# find root loc in tin
idx_root_tin=tin.index(pre[0])
root.left=self.reConstructBinaryTree(pre[1:idx_root_tin+1],tin[0:idx_root_tin])
root.right=self.reConstructBinaryTree(pre[idx_root_tin+1:],tin[idx_root_tin+1:])
return root
需要了解python的語法,可看python項目應用實例(一)常見運算|維度|基本元素|基本語法|函數 中的八
python中的分片與步長 https://www.cnblogs.com/kuqs/p/6541723.html
分片的時候,[開始位置:結束位置+1]這個要搞清楚
三、二叉樹編程彙總
3.1 樹的子結構
輸入兩棵二叉樹A,B,判斷B是不是A的子結構。(ps:我們約定空樹不是任意一個樹的子結構)
遞歸非常重要,並且可能存在遞歸的嵌套。這種題需要反覆做,反覆體會。
c++樹的子結構
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
bool RootHasSubTree(TreeNode* pRoot1, TreeNode* pRoot2){
if(pRoot2==NULL)return true;
if(pRoot1==NULL)return false;
if(pRoot1->val==pRoot2->val)return RootHasSubTree(pRoot1->left,pRoot2->left)&&RootHasSubTree(pRoot1->right,pRoot2->right);
else return false;
}
bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
{
if(pRoot1==NULL||pRoot2==NULL)return false;
bool result=false;
if(pRoot1->val==pRoot2->val)result=RootHasSubTree(pRoot1,pRoot2);
if(!result)result=HasSubtree(pRoot1->left,pRoot2);
if(!result)result=HasSubtree(pRoot1->right,pRoot2);
return result;
}
};
3.2 鏡像二叉樹
對於二叉樹問題,掌握遞歸則基本掌握了二叉樹的解法。
操作給定的二叉樹,將其變換爲源二叉樹的鏡像。(數的子節點也要倒順序)
c++鏡像二叉樹
跟先遞歸調用mirror,再進行左右交換一樣。
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
void Mirror(TreeNode *pRoot) {
if(pRoot==NULL)return;
TreeNode * temp=pRoot->left;
pRoot->left=pRoot->right;
pRoot->right=temp;
Mirror(pRoot->left);
Mirror(pRoot->right);
return;
}
};
3.3 按層打印二叉樹
從上往下打印出二叉樹的每個節點,同層節點從左至右打印。
解析:這叫寬度優先遍歷。
c++按層打印二叉樹
需要掌握queue的操作:https://www.cnblogs.com/danielStudy/p/6726491.html
注意queue的用法與stack類似。也是.push()與.pop(),但是第一個元素是.front(),而stack的頂元素爲.top()
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
vector<int> PrintFromTopToBottom(TreeNode* root) {
vector<int> value_queue;
if(root==NULL)return value_queue;
queue<TreeNode*> node_que;
node_que.push(root);
while(!node_que.empty()){
TreeNode* current_node=node_que.front();
value_queue.push_back(current_node->val);
if(current_node->left!=NULL)node_que.push(current_node->left);
if(current_node->right!=NULL)node_que.push(current_node->right);
node_que.pop();
}
return value_queue;
}
};
3.4 判斷排序二叉樹後序遍歷
輸入一個整數數組,判斷該數組是不是某二叉搜索樹的後序遍歷的結果。如果是則輸出Yes,否則輸出No。假設輸入的數組的任意兩個數字都互不相同。
解析:二叉樹可以用於排序,並且其中有很多知識點。排序二叉樹的值,後序遍歷,則一定是,順序一定是{小值,大值,中間值}
任意一個節點,只要其左邊的值順序一定是{比它小,比它大}
c++判斷排序二叉樹後序遍歷
如果掌握了前面的知識點,則這個可以很快做出來。
class Solution {
public:
bool VerifySquenceOfBST(vector<int> sequence) {
int sequence_size=sequence.size();
if(sequence_size<1)return false;
for(int seq_idx=sequence_size-1;seq_idx>0;seq_idx--){
int compare_idx=0;
while(sequence[compare_idx]<sequence[seq_idx])compare_idx++;
while(sequence[compare_idx]>sequence[seq_idx])compare_idx++;
if(compare_idx!=seq_idx)return false;
}
return true;
}
};
3.5 二叉樹的深度
輸入一棵二叉樹,求該樹的深度。從根結點到葉結點依次經過的結點(含根、葉結點)形成樹的一條路徑,最長路徑的長度爲樹的深度。
解析:基本上,二叉樹的題,都需要運用遞歸,思路對的話,代碼量並不大。
c++二叉樹深度
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
int TreeDepth(TreeNode* pRoot)
{
if(pRoot==NULL)return 0;
int left_depth=TreeDepth(pRoot->left);
int right_depth=TreeDepth(pRoot->right);
if(left_depth>right_depth)return left_depth+1;
else return right_depth+1;
}
};
3.6 平衡二叉樹
輸入一棵二叉樹,判斷該二叉樹是否是平衡二叉樹。
解析:平衡二叉樹的左右節點深度差值不能超過1.
c++平衡二叉樹
直接算出深度即可。abs表示絕對值函數,可以直接用。直接套用上題深度的測試。寫法上可以更高明一點。
class Solution {
public:
int tree_depth(TreeNode* pRoot){
if(pRoot==NULL)return 0;
int left_depth=tree_depth(pRoot->left);
int right_depth=tree_depth(pRoot->right);
if(left_depth>right_depth)return left_depth+1;
else return right_depth+1;
}
bool IsBalanced_Solution(TreeNode* pRoot) {
if(pRoot==NULL)return true;
if(abs(tree_depth(pRoot->left)-tree_depth(pRoot->right))>1)return false;
else return true;
}
};
選擇結構的寫法可以如下:
class Solution {
public:
int tree_depth(TreeNode* pRoot){
if(pRoot==NULL)return 0;
int left_depth=tree_depth(pRoot->left);
int right_depth=tree_depth(pRoot->right);
return (left_depth>right_depth)? left_depth+1:right_depth+1;
}
bool IsBalanced_Solution(TreeNode* pRoot) {
if(pRoot==NULL)return true;
return (abs(tree_depth(pRoot->left)-tree_depth(pRoot->right))>1)? false:true;
}
};
3.7 對稱二叉樹
請實現一個函數,用來判斷一顆二叉樹是不是對稱的。注意,如果一個二叉樹同此二叉樹的鏡像是同樣的,定義其爲對稱的。
解析:原題給出的函數接口,只有一個節點,bool isSymmetrical(TreeNode* pRoot),並不利於遞歸調用,可以多設一個函數接口實現調用:
c++對稱二叉樹
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
*/
class Solution {
public:
bool isSymmetrical(TreeNode* pRoot)
{
if(pRoot==NULL)return true;
return isSymmetrical_node(pRoot->left,pRoot->right);
}
bool isSymmetrical_node(TreeNode* pLeft,TreeNode* pRight){
if(pLeft==NULL&&pRight==NULL)return true;
else if(pLeft==NULL||pRight==NULL)return false;
if(pLeft->val==pRight->val)
return isSymmetrical_node(pLeft->right,pRight->left)&&isSymmetrical_node(pLeft->left,pRight->right);
else return false;
}
};
四、堆排序
堆(英語:heap)是計算機科學中一類特殊的數據結構的統稱。堆通常是一個可以被看做一棵樹的數組對象。若將和此次序列對應的一維數組(即以一維數組作此序列的存儲結構)看成是一個完全二叉樹,則堆的含義表明,完全二叉樹中所有非終端結點的值均不大於(或不小於)其左、右孩子結點的值。由此,若序列{k1,k2,…,kn}是堆,則堆頂元素(或完全二叉樹的根)必爲序列中n個元素的最小值(或最大值)。
樹形選擇排序方法尚有輔助存儲空間較多、和“最大值”進行多餘比較等缺點。爲了彌補,威洛姆斯(J. willioms)在1964年提出了另一種形式的選擇排序——堆排序。
https://www.cnblogs.com/chengxiao/p/6129630.html
堆排序是利用堆這種數據結構而設計的一種排序算法,堆排序是一種選擇排序,它的最壞,最好,平均時間複雜度均爲O(nlogn),它也是不穩定排序。首先簡單瞭解下堆結構。
堆排序運用完全二叉樹的性質,將二叉樹的節點地址編號爲地址,不用構建帶有指針的二叉樹,只用數組即可實現二叉樹。
爲什麼不穩定排序:舉一個反例即可,兩個葉子節點上的數相同,但是葉子節點有可能進行swap,也有可能不進行swap,就可能無法保持穩定。
4.1 大頂堆與小頂堆
排序中的堆與隊列和程序中的堆與棧不一樣,注意區分。
堆是具有以下性質的完全二叉樹:每個結點的值都大於或等於其左右孩子結點的值,稱爲大頂堆;或者每個結點的值都小於或等於其左右孩子結點的值,稱爲小頂堆。如下圖:
注意只需要父節點大於子節點,並不需要子節點之間進行排序。
4.2 基本思想與步驟
堆排序的基本思想是:將待排序序列構造成一個大頂堆,此時,整個序列的最大值就是堆頂的根節點。將其與末尾元素進行交換,此時末尾就爲最大值。然後將剩餘n-1個元素重新構造成一個堆,這樣會得到n個元素的次小值。如此反覆執行,便能得到一個有序序列了
再簡單總結下堆排序的基本思路:
a.將無需序列構建成一個堆,根據升序降序需求選擇大頂堆或小頂堆;
b.將堆頂元素與末尾元素交換,將最大元素"沉"到數組末端;
c.重新調整結構,使其滿足堆定義,然後繼續交換堆頂元素與當前末尾元素,反覆執行調整+交換步驟,直到整個序列有序。
步驟一、構造初始堆
將給定無序序列構造成一個大頂堆(一般升序採用大頂堆,降序採用小頂堆)。容易理解,大頂堆在交換之後最後的元素最大,小頂堆在交換之後最後的元素最小。
a.假設給定無序序列結構如下
此時我們從最後一個非葉子結點開始(葉結點自然不用調整,第一個非葉子結點 arr.length/2-1=5/2-1=1,也就是下面的6結點),從左至右,從下至上進行調整。(注意關於堆的性質,有一個性質是,第一個非葉子節點的編號=length/2 -1)
找到第二個非葉節點4,由於[4,9,8]中9元素最大,4和9交換。(堆之中是三個數字進行比較)
這時,交換導致了子根[4,5,6]結構混亂,繼續調整,[4,5,6]中6最大,交換4和6。
此時,我們就將一個無需序列構造成了一個大頂堆。
步驟二、頂端與末尾交換
頂端與末尾元素交換之後,迭代進行步驟一
將堆頂元素與末尾元素進行交換,使末尾元素最大。然後繼續調整堆,再將堆頂元素與末尾元素交換,得到第二大元素。如此反覆進行交換、重建、交換。
a.將堆頂元素9和末尾元素4進行交換
b.重新調整結構,使其繼續滿足堆定義
c.再將堆頂元素8與末尾元素5進行交換,得到第二大元素8.
後續過程,繼續進行調整,交換,如此反覆進行,最終使得整個序列有序
4.3 代碼
https://www.cnblogs.com/AlgrithmsRookie/p/5896603.html
創建堆
此爲創建大頂堆的代碼,這種遍歷是自底向頂的遍歷
void make_heap(int *a, int len)
{
for(int i = (len-1)/2; i >= 0; --i) //遍歷每個 非葉子節點
adjust_heap(a, i, len);//不用考慮那麼多, 用面向對象的思鄉去考慮,
} //這個函數的作用就是用來使當前節點的子樹符合堆的規律
調整當前非葉子節點
當前非葉子節點構建大頂堆,
void adjust_heap(int* a, int node, int size)
{
int left = 2*node + 1;
int right = 2*node + 2;
int max = node;
if( left < size && a[left] > a[max])
max = left;
if( right < size && a[right] > a[max])
max = right;
if(max != node)
{
swap( a[max], a[node]); //交換節點
adjust_heap(a, max, size); //遞歸
}
}
最後的遞歸表示,如果node與max的值交換之後,新的a[max]可能比它的兩個兒子小,即管不住它的兩個兒子,因此需要繼續遞歸。
同時,考慮到如果node爲葉子節點,則left和right<size這個判斷就能保證了。
完整代碼
#include <iostream>
using namespace std;
void adjust_heap(int* a, int node, int size)
{
int left = 2*node + 1;
int right = 2*node + 2;
int max = node;
if( left < size && a[left] > a[max])
max = left;
if( right < size && a[right] > a[max])
max = right;
if(max != node)
{
swap( a[max], a[node]);
adjust_heap(a, max, size);
}
}
void heap_sort(int* a, int len)
{
for(int i = len/2 -1; i >= 0; --i)
adjust_heap(a, i, len);
for(int i = len - 1; i >= 0; i--)
{
swap(a[0], a[i]); // 將當前最大的放置到數組末尾
adjust_heap(a, 0 , i); // 將未完成排序的部分繼續進行堆排序
}
}
int main()
{
int a[10] = {3, 2, 7, 4, 2, -999, -21, 99, 0, 9 };
int len= sizeof(a) / sizeof(int);
for(int i = 0; i < len; ++i)
cout << a[i] << ' ';
cout << endl;
heap_sort(a, len);
for(int i = 0; i < len; ++i)
cout << a[i] << ' ';
cout << endl;
return 0;
}
五、紅黑樹
後續更新
5.1 定義
https://baike.baidu.com/item/%E7%BA%A2%E9%BB%91%E6%A0%91/2413209?fr=aladdin
紅黑樹(Red Black Tree) 是一種自平衡二叉查找樹,典型的用途是實現關聯數組。它雖然是複雜的,但它的最壞情況運行時間也是非常良好的,並且在實踐中是高效的: 它可以在O(log n)時間內做查找,插入和刪除,這裏的n 是樹中元素的數目。