樹
@
一.抽象數據類型
1.順序存儲
使用數組存儲
父親索引為 n
左孩子 2*n 右孩子 2*n+1
2.鏈式存儲
typedef struct TNode *Position;
typedef Position BinTree; /* 二叉樹類型 */
struct TNode{ /* 樹結點定義 */
ElementType Data; /* 結點數據 */
BinTree Left; /* 指向左子樹 */
BinTree Right; /* 指向右子樹 */
};
二、二叉樹的性質
1.二叉樹第i層最大結點數為:2^(i-1),i>=1
2.深度為k的二叉樹最大結點總數為:2^k-1,k>=1
3.對任何非空二叉樹T,若n0表示葉子結點個數、n2是度為2的非葉子結點個數,那么二者滿足關系n0=n2+1
三、二叉樹的遍歷
3.1.遞歸
void PreOrderTraversal(BinTree BT){
if(BT){
printf("%d",BT->Data);
PreOrderTraversal(BT->Left);
PreOrderTraversal(BT->Right);
}
}
中序后序同理,把打印放在中間和后面,這里不加以贅述。
3.2.非遞歸
以鏈式中序為例
void InOrderTraversal(BinTree BT){
BinTree T=BT;
Stack S=CreatSatck(MaxSize);
while(T||!IsEmpty(S)){
Push(S,T);
T=T->Left;
}
if(!IsEmpty(S)){
T=Pop(S);
printf("%5d",T->Data);
T=T->Right;
}
}
3.3.利用隊列進行層序遍歷
void levelOrderTraversal(Tree *tr){
queue<Tree*> que;
Tree *t=tr;
if(t==NULL) return;
que.push(t);
while(!que.empty()){
t=que.front();
que.pop();
printf("%d\n",t->val);
if(t->left!=NULL) que.push(t->left);
if(t->right!=NULL) que.push(t->right);
}
}
3.4.已知先序中序求后序
#include <cstdio>
using namespace std;
int post[] = {3, 4, 2, 6, 5, 1};
int in[] = {3, 2, 4, 1, 6, 5};
void pre(int root, int start, int end) {
if(start > end) return ;
int i = start;
while(i < end && in[i] != post[root]) i++;
printf("%d ", post[root]);
pre(root - 1 - end + i, start, i - 1);
pre(root - 1, i + 1, end);
}
int main() {
pre(5, 0, 5);
return 0;
}
3.5.已知中序后序求先序
#include <cstdio>
using namespace std;
int pre[] = {1, 2, 3, 4, 5, 6};
int in[] = {3, 2, 4, 1, 6, 5};
void post(int root, int start, int end) {
if(start > end)
return ;
int i = start;
while(i < end && in[i] != pre[root]) i++;
post(root + 1, start, i - 1);
post(root + 1 + i - start, i + 1, end);
printf("%d ", pre[root]);
}
int main() {
post(0, 0, 5);
return 0;
}
3.6.先序構建樹
TreeNode* buildTree(int root, int start, int end) {
if(start > end) return NULL;
int i = start;
while(i < end && in[i] != pre[root]) i++;
TreeNode* t = new TreeNode();
t->left = buildTree(root + 1, start, i - 1);
t->right = buildTree(root + 1 + i - start, i + 1, end);
t->data = pre[root];
return t;
}
三、活用樹的遍歷
3.1.PAT Advanced 1020 Tree Traversals
參考 https://pintia.cn/problem-sets/994805342720868352/problems/994805485033603072
這是一道考察樹的構成,后序中序轉前序,前序構造樹,樹層序遍歷的一道題目,知識點考察很多
原題翻譯:
已知后序遍歷,中序遍歷,求層序遍歷
輸入:一共有多少值
第一行為后序遍歷,第二行為中序遍歷
輸出:層序遍歷
Sample Input:
7
2 3 1 5 7 6 4
1 2 3 4 5 6 7
Sample Output:
4 1 6 3 5 7 2
根據二中的方法,我們可以進行活用
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
struct TreeNode{//樹的抽象類型
int val;
TreeNode *left;
TreeNode *right;
};
vector<int> pre,in,post,ans;
queue<TreeNode*> que;
void preOrder(int root,int start,int end){//由中序后序建立前序
if(start>end) return;
int i=0;
while(i<=end&&in[i]!=post[root]) i++;
pre.push_back(post[root]);
preOrder(root-1-end+i,start,i-1);
preOrder(root-1,i+1,end);
}
TreeNode* buildTree(int root,int start,int end){//由前序中序構建樹
if(start>end) return NULL;
int i=0;
TreeNode *t=new TreeNode();
while(i<=end&&in[i]!=pre[root]) i++;
t->val=pre[root];
t->left=buildTree(root+1,start,i-1);
t->right=buildTree(root+1+i-start,i+1,end);
return t;
}
void levelOrder(TreeNode *tree){//層序遍歷樹
que.push(tree);
while(!que.empty()){
TreeNode *tmp=que.front();
ans.push_back(tmp->val);
que.pop();
if(tmp->left!=NULL) que.push(tmp->left);
if(tmp->right!=NULL) que.push(tmp->right);
}
}
int main()
{
int N;
scanf("%d",&N);
post.resize(N);in.resize(N);
for(int i=0;i<N;i++) scanf("%d",&post[i]);
for(int i=0;i<N;i++) scanf("%d",&in[i]);
preOrder(N-1,0,N-1);
TreeNode *tree=buildTree(0,0,N-1);
levelOrder(tree);
for(int i=0;i<ans.size();i++)
if(i!=ans.size()-1) cout<<ans[i]<<" ";
else cout<<ans[i];
system("pause");
return 0;
}
查看柳婼大神的博客,我們可以知道,有更簡單的方法,僅僅加一個索引值,便可以達到相同的效果,代碼如下:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct node{
int index;
int val;
};
bool cmp(node n1,node n2){
return n1.index<n2.index;
}
vector<int> in,post;
vector<node> ans;
void preOrder(int root,int start,int end,int index){
if(start>end) return;
int i=0;
while(i<=end&&in[i]!=post[root]) i++;
ans.push_back({index,post[root]});
preOrder(root-1-end+i,start,i-1,2*index+1);
preOrder(root-1,i+1,end,2*index+2);
}
int main()
{
int N;
scanf("%d",&N);
post.resize(N);in.resize(N);
for(int i=0;i<N;i++) scanf("%d",&post[i]);
for(int i=0;i<N;i++) scanf("%d",&in[i]);
preOrder(N-1,0,N-1,0);
sort(ans.begin(),ans.end(),cmp);
for(int i=0;i<N;i++)
if(i!=N-1) printf("%d ",ans[i].val);
else printf("%d",ans[i].val);
system("pause");
return 0;
}
3.2.PAT Advanced 1086 Tree Traversals Again 參考PAT官網原題
這道題比上面一題簡單,簡單敘述
給先序遍歷的進棧出棧過程,求后序遍歷。
輸入:數量 進棧出棧過程
輸出:后序遍歷
Sample Input:
6
Push 1
Push 2
Push 3
Pop
Pop
Push 4
Pop
Pop
Push 5
Push 6
Pop
Pop
Sample Output:
3 4 2 6 5 1
我們可以知道,先序遍歷的出棧就是中序遍歷打印,於是代碼如下:
#include <iostream>
#include <vector>
#include <stack>
#include <cstring>
using namespace std;
vector<int> pre,in,post,val;
void postorder(int root,int start,int end){//中序,后序找前序
if(start>end) return ;
int root_index=0;
while(root_index<=end&&in[root_index]!=pre[root]) root_index++;
postorder(root+1,start,root_index-1);
postorder(root+1+root_index-start,root_index+1,end);
post.push_back(pre[root]);
}
int main()
{
stack<int> sta;
int k=0,N;
char ch[5];int num;
scanf("%d",&N);
while(~scanf("%s",&ch)){
if(strlen(ch)==4) {
scanf("%d",&num);
pre.push_back(num);
sta.push(num);
}else{
in.push_back(sta.top());
sta.pop();
//if(in.size()==N) break;//測試代碼的時候,可以把這句加上,因為官方使用的是文件測,所以支持~scanf的寫法
}
}
postorder(0,0,N-1);
for(int i=0;i<N;i++)
if(i!=N-1) printf("%d ",post[i]);
else printf("%d",post[i]);
system("pause");
return 0;
}
四、BST樹
原型以及增刪
C語言BST樹原型,BST樹在進行增刪的時候效率沒有鏈表高,但是在查找的時候比較快,這邊以BST原型,用C程序構造一個BST樹。
#ifndef BSTTREE_H_INCLUDED
#define BSTTREE_H_INCLUDED
#define ElementType int
#define Position BSTTree*
typedef struct BSTTree{
ElementType Data;
struct BSTTree *Left;
struct BSTTree *Right;
}BSTTree;
Position FindMin(BSTTree *bstTree){//遞歸查找
if(!bstTree) return NULL;
else if(!bstTree->Left) return bstTree;
else return FindMin(bstTree->Left);
}
Position FindMax(BSTTree *bstTree){//循環查找
if(bstTree)
while(bstTree->Right) bstTree=bstTree->Right;
return bstTree;
}
BSTTree* Insert(ElementType x,BSTTree *bstTree){
if(bstTree){
bstTree=malloc(sizeof(struct BSTTree));
bstTree->Data=x;
bstTree->Left=NULL;
bstTree->Right=NULL;
}else{
if(x<bstTree->Data) bstTree->Left=Insert(x,bstTree->Left);
else if(x>bstTree->Data) bstTree->Right=Insert(x,bstTree->Right);
//else 啥也不做
}
return bstTree;
}
BSTTree* Delete(ElementType x,BSTTree *bstTree){
Position Tmp;
if(!bstTree) printf("not find");
else if(x<bstTree->Data) bstTree->Left=Delete(x,bstTree->Left);//左遞歸刪除
else if(x>bstTree->Data) bstTree->Right=Delete(x,bstTree->Right);//右遞歸刪除
else
if(bstTree->Left&&bstTree->Right){//左右兩個孩子
Tmp=FindMin(bstTree->Right);//右子樹找最小元素填充刪除節點
bstTree->Data=Tmp->Data;
bstTree->Right=Delete(bstTree->Data,bstTree->Right);//繼續刪除右子樹最小元素
}else{//有一個孩子或者沒有孩子
Tmp=bstTree;
if(!bstTree->Left)
bstTree=bstTree->Right;//直接賦值有孩子
else if(!bstTree->Left)
bstTree=bstTree->Left;//直接賦值左孩子
free(Tmp);//進行釋放
}
return bstTree;
}
#endif // BSTTREE_H_INCLUDED
五、AVL樹
5.1.AVL樹概念
什么是AVL樹,比BST樹多了一個平衡因子(Balance Factor),簡稱BF:BF(T)=hl-hr,其中hl和hr為左右子樹的高度。
當平衡因子大於1的時候,我們就可以進行旋轉操作
1.RR旋轉(右單旋)

2.LL旋轉(左單旋)
3.LR旋轉(左右雙旋)
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-46YFPG97-1571396745491)(C:\Users\74302\AppData\Roaming\Typora\typora-user-images\1571053306116.png)]
5.2.AVL樹原型
#define ElementType int
typedef struct AVLNode *Position;
typedef Position AVLTree; /* AVL樹類型 */
struct AVLNode{
ElementType Data; /* 結點數據 */
AVLTree Left; /* 指向左子樹 */
AVLTree Right; /* 指向右子樹 */
int Height; /* 樹高 */
};
5.3.AVL樹LL旋轉
AVLTree SingleLeftRotation(AVLTree A){
/* 注意:A必須有一個左子結點B */
/* 將A與B做左單旋,更新A與B的高度,返回新的根結點B */
AVLTree B = A->Left;
A->Left = B->Right;
B->Right = A;
A->Height = Max( GetHeight(A->Left), GetHeight(A->Right) ) + 1;
B->Height = Max( GetHeight(B->Left), A->Height ) + 1;
return B;
}
5.4.AVL樹LR旋轉
AVLTree DoubleLeftRightRotation ( AVLTree A )
{ /* 注意:A必須有一個左子結點B,且B必須有一個右子結點C */
/* 將A、B與C做兩次單旋,返回新的根結點C */
/* 將B與C做右單旋,C被返回 */
A->Left=SingleRightRotation(A->Left);
/* 將A與C做左單旋,C被返回 */
return SingleLeftRotation(A);
}
5.5.AVL樹插入函數
AVLTree Insert( AVLTree T, ElementType X )
{ /* 將X插入AVL樹T中,並且返回調整后的AVL樹 */
if ( !T ) { /* 若插入空樹,則新建包含一個結點的樹 */
T = (AVLTree)malloc(sizeof(struct AVLNode));
T->Data = X;
T->Height = 0;
T->Left = T->Right = NULL;
} /* if (插入空樹) 結束 */
else if ( X < T->Data ) {
/* 插入T的左子樹 */
T->Left = Insert( T->Left, X);
/* 如果需要左旋 */
if ( GetHeight(T->Left)-GetHeight(T->Right) == 2 )
if ( X < T->Left->Data )
T = SingleLeftRotation(T); /* 左單旋 */
else
T = DoubleLeftRightRotation(T); /* 左-右雙旋 */
} /* else if (插入左子樹) 結束 */
else if ( X > T->Data ) {
/* 插入T的右子樹 */
T->Right = Insert( T->Right, X );
/* 如果需要右旋 */
if ( GetHeight(T->Left)-GetHeight(T->Right) == -2 )
if ( X > T->Right->Data )
T = SingleRightRotation(T); /* 右單旋 */
else
T = DoubleRightLeftRotation(T); /* 右-左雙旋 */
} /* else if (插入右子樹) 結束 */
/* else X == T->Data,無須插入 */
/* 別忘了更新樹高 */
T->Height = Max( GetHeight(T->Left), GetHeight(T->Right) ) + 1;
return T;
}
六、BST樹和AVL樹練習
6.1.浙大習題7-4 是否是同一棵二叉搜索樹
給定一個插入序列就可以唯一確定一棵二叉搜索樹。然而,一棵給定的二叉搜索樹卻可以由多種不同的插入序列得到。例如分別按照序列{2, 1, 3}和{2, 3, 1}插入初始為空的二叉搜索樹,都得到一樣的結果。於是對於輸入的各種插入序列,你需要判斷它們是否能生成一樣的二叉搜索樹。
輸入格式:
輸入包含若干組測試數據。每組數據的第1行給出兩個正整數N (≤10)和L,分別是每個序列插入元素的個數和需要檢查的序列個數。第2行給出N個以空格分隔的正整數,作為初始插入序列。最后L行,每行給出N個插入的元素,屬於L個需要檢查的序列。
簡單起見,我們保證每個插入序列都是1到N的一個排列。當讀到N為0時,標志輸入結束,這組數據不要處理。
輸出格式:
對每一組需要檢查的序列,如果其生成的二叉搜索樹跟對應的初始序列生成的一樣,輸出“Yes”,否則輸出“No”。
輸入樣例:
4 2
3 1 4 2
3 4 1 2
3 2 4 1
2 1
2 1
1 2
0
輸出樣例:
Yes
No
No
這道題,僅僅需要進行構造一棵BST樹,然后進行判斷先序遍歷的打印即可,AC代碼如下。
#include <iostream>
using namespace std;
string traver_ini,traver_cmp;
struct BST{
int val;
BST *left;
BST *right;
};
BST *ini,*cmp;
BST* BST_insert(BST *b,int val){
if(b==NULL) {
b=new BST();
b->val=val;
return b;
}
if(val<b->val) b->left=BST_insert(b->left,val);
else if(val>b->val) b->right=BST_insert(b->right,val);
return b;
}
void pre(BST *b,string& str){
if(b==NULL) return;
str+=(b->val+'0');
pre(b->left,str);
pre(b->right,str);
}
int main()
{
int M,N,tmp;
while(1){
scanf("%d",&M);
if(M==0) break;
scanf("%d\n",&N);
ini=NULL;
for(int i=0;i<M;i++){
scanf("%d",&tmp);
ini=BST_insert(ini,tmp);
}
traver_ini="";
pre(ini,traver_ini);
while(N--){
cmp=NULL;
for(int i=0;i<M;i++){
scanf("%d",&tmp);
cmp=BST_insert(cmp,tmp);
}
traver_cmp="";
pre(cmp,traver_cmp);
if(traver_cmp==traver_ini) printf("Yes\n");
else printf("No\n");
}
}
return 0;
}
6.2.PAT Advanced 1066 Root of AVL Tree (25 分) 參考PAT官網
簡單敘述:
給定插入元素個數n
插入n個元素后,輸出根節點元素值
Sample Input 1:
5
88 70 61 96 120
Sample Output 1:
70
Sample Input 2:
7
88 70 61 96 120 90 65
Sample Output 2:
88
解題方法,構造一棵AVL樹,然后插入,求根即可
#include <iostream>
using namespace std;
struct node{//AVL節點原型
int val;
node *left,*right;
};
node *rotateRight(node *root){//右多,進行RR旋轉
node *t=root->right;
root->right=t->left;
t->left=root;
return t;
}
node *rotateLeft(node *root){//左多,進行LL旋轉
node *t=root->left;
root->left=t->right;
t->right=root;
return t;
}
node *rotateLeftRight(node *root){//左多,進行LR旋轉
root->left=rotateRight(root->left);
return rotateLeft(root);
}
node *rotateRightLeft(node *root){//右多,進行RL旋轉
root->right=rotateLeft(root->right);
return rotateRight(root);
}
int getHeight(node *root){//獲得高度
if(root==NULL) return 0;
return max(getHeight(root->left),getHeight(root->right))+1;
}
node *insert(node *root,int val){
if(root==NULL){//空的時候
root=new node();
root->val=val;
root->left=root->right=NULL;
}else if(val<root->val){//插左邊
root->left=insert(root->left,val);
if(getHeight(root->left)-getHeight(root->right)==2)
root=val<root->left->val?rotateLeft(root):rotateLeftRight(root);
//如果比左還要小,只需要進行LL旋轉,否則需要LR旋轉
}else{//插右邊
root->right=insert(root->right,val);
if(getHeight(root->right)-getHeight(root->left)==2)
root=val>root->right->val?rotateRight(root):rotateRightLeft(root);
//如果比右還要打,只需進行RR旋轉,否則需要RL旋轉
}
return root;
}
int main()
{
int n,tmp;cin>>n;
node* root=NULL;
for(int i=0;i<n;i++){
cin>>tmp;
root=insert(root,tmp);
}
cout<<root->val;
system("pause");
return 0;
}
七、堆
7.1.創建堆
void createHeap(){
for(int i=n/2;i>=1;i--)
downAjust(i,n);
}
7.2.向下調整
思想是,low代表孩子,high代表極限。我們在[low,high]范圍進行調整,low和low+1為2*low的孩子。如果沒有交換,那就break掉,交換了還要和上面父親去比較。
void downAdjust(int low,int high){
int i=low,j=i*2;//i為要調整的節點,j為左孩子
while(j<=high){
if(j+1<=high && heap[j+1]>heap[j]) j=j+1;
if(heap[j]>heap[i]){
swap(heap[j],heap[i]);
i=j;j=i*2;
}else break;
}
}
7.3.刪除堆頂元素
void deleteTop(){
heap[1]=heap[n--];//用第n個數進行覆蓋
downAdjust(1,n);//之后進行向下調整第一個數
}
7.4.增加一個元素
void insert(int x){
heap[++n]=x;
upAdjust(1,n);
}
7.5.向上調整
void upAdjust(int low,int high){
int i=high,j=i/2;
while(j>=low){
if(heap[j]<heap[i]){
swap(heap[j],heap[i]);
i=j;j=i/2;
}else break;
}
}
7.6.堆排序
void heapSort(){
createHeap();
for(int i=n;i>=2;i--){
swap(heap[i],heap[1]);
downAdjust(1,i-1);
}
}
7.7.復用型heap.h文件
#ifndef HEAP_H_INCLUDED
#define HEAP_H_INCLUDED
#define maxn 1000
using namespace std;//這里面有swap函數
int heap[maxn];
void upAdjust(int low,int high){
int i=high,j=i/2;
while(j>=low){
if(heap[j]<heap[i]){
swap(heap[j],heap[i]);
i=j;j=i/2;
}else break;
}
}
void downAdjust(int low,int high){
int i=low,j=i*2;//i為要調整的節點,j為左孩子
while(j<=high){
if(j+1<=high && heap[j+1]>heap[j]) j=j+1;
if(heap[j]>heap[i]){
swap(heap[j],heap[i]);
i=j;j=i*2;
}else break;
}
}
void createHeap(int n){
for(int i=n/2;i>=1;i--)
downAdjust(i,n);
}
void deleteTop(int n){
heap[1]=heap[n--];//用第n個數進行覆蓋
downAdjust(1,n);//之后進行向下調整第一個數
}
void insert(int x,int n){
heap[++n]=x;
upAdjust(1,n);
}
void heapSort(int n){
createHeap(n);
for(int i=n;i>=2;i--){
swap(heap[i],heap[1]);
downAdjust(1,i-1);
}
}
#endif // HEAP_H_INCLUDED
7.8.測試文件
#include <iostream>
#include "heap.h"
using namespace std;
int main(){
extern int heap[maxn];
extern int n;
scanf("%d",&n);
/**我們的堆序列是從[1,n]的*/
for(int i=1;i<=n;i++) scanf("%d",&heap[i]);
createHeap();//生成大根堆
insert(4);//插入堆頂元素
deleteTop();//刪除堆頂
heapSort();//排序
for(int i=1;i<=n;i++) printf("%d%s",heap[i],i==n?"\n":" ");
system("pause");
return 0;
}
