三種判斷完全二叉樹的方法的實現與分析
概述
Dijkstra演算法、Prim演算法等都要用堆才能優化,幾乎每次都要考到的二叉排序樹的效率也要藉助平衡性來提高,而平衡性基於完全二叉樹。對於一棵完全二叉樹,葉子節點只可能出現在最下層和倒數第二層,且最下層的葉子節點集中在樹的最左部。
-
層次遍歷法
基本思路
-
實現
//CompleteBiTree.c //建立一棵樹,並判斷其是不是完全二叉樹 //主要用到佇列的方式,並採用迴圈佇列 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/time.h> #include <sys/types.h> #include <time.h> #define MAX_NUM_OF_QUEUE 20 #define YES 1 #define NO 0 typedef struct node { int num; struct node *leftSon; struct node *rightSon; }biNode; biNode* CreateBiTree(int array[],int n) //建樹 { biNode *root; if((-1 == array[n]) || (n >= MAX_NUM_OF_QUEUE)) //以-1標記空節點 return NULL; root = (biNode *)malloc(sizeof(biNode)); root->num = array[n]; root->leftSon = CreateBiTree(array,2*n+1); //遞迴左子樹建樹 root->rightSon = CreateBiTree(array,2*n+2); //遞迴右子樹建樹 return root; } int Adjust(biNode *root) { biNode queue[MAX_NUM_OF_QUEUE]; int front = -1; int rear = 0; int flag = 0; if(root == NULL) return NO; queue[rear] = *root; while(front != rear) { front = (front + 1)%MAX_NUM_OF_QUEUE; if((queue[front].leftSon == NULL) && (queue[front].rightSon != NULL)) return NO; if(flag == 0) //標記是否滿足判斷的條件 { if(((queue[front].leftSon == NULL) && (queue[front].rightSon == NULL)) || ((queue[front].leftSon != NULL) && (queue[front].rightSon == NULL))) flag = 1; } else //判斷條件滿足時開始判斷 { if((queue[front].leftSon != NULL) || (queue[front].rightSon != NULL)) return NO; } if(queue[front].leftSon != NULL) { rear = (rear + 1)%MAX_NUM_OF_QUEUE; queue[rear] = *queue[front].leftSon; } if(queue[front].rightSon != NULL) { rear = (rear + 1)%MAX_NUM_OF_QUEUE; queue[rear] = *queue[front].rightSon; } } return YES; } int main(int argc,char **argv) { int array[MAX_NUM_OF_QUEUE] = {15,1,35,20,3,46,9,7,9,11,21,8,4,13,-1,-1,-1,-1,-1,-1}; biNode *root; int i; long t1,t2; struct timeval *time1,*time2; time1 = (struct timeval*)malloc(sizeof(struct timeval)); time2 = (struct timeval*)malloc(sizeof(struct timeval)); gettimeofday(time1,NULL); t1 = time1->tv_sec * 1000000 + time1->tv_usec; root = CreateBiTree(array,0); i = Adjust(root); if(i == YES) printf("Yes\n"); else printf("No\n"); gettimeofday(time2,NULL); t2 = time2->tv_sec * 1000000 + time2->tv_usec; printf("time:%ld\n",t2-t1); free(time1); free(time2); }
-
分析
時間複雜度:O(n)
其中,n為節點個數。最壞情況下,需要對所有節點進行入隊出隊操作,時間效率的損耗也主要在入隊和出隊操作上。
-
遞迴中序遍歷
基本思路
a.最後一層以上的節點必須組成一棵滿樹; b.最後一層的節點必須全部集中在最右邊;
b.條件利用到中序遍歷的一個特點,就是中序遍歷出來的葉子節點全部排在陣列中的第一個元素開始間隔排放的;
具體做法
前提:建樹時必須標記節點的層數
-
遞迴中序遍歷
k;之後,邊遍歷邊記錄層數小於K的節點的個數M和第K層節點的個數N。最後,利用公式比較M是否等於,如果不是,則可以判斷不是一棵完全二叉樹,如果是,轉c繼續判斷;
判斷最後一層節點是否集中在左部
N個K層元素。則需要驗證陣列下標分別為0,2,4,6······(N-1)*2的元素的層數是否為最大層數。如果都是,則為完全二叉樹,否則不是。
-
實現
//DeepOrderAdjust.c //用深度遍歷的方法判斷完全二叉樹 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <math.h> #include <sys/time.h> #include <sys/types.h> #include <time.h> #define MAX_NUM_OF_QUEUE 20 #define YES 1 #define NO 0 typedef struct node { int num; int floor; struct node *leftSon; struct node *rightSon; }biNode; static int MaxFloor = 0; //最大層數 static int NodeCount = 0; //最底層之上的節點個數 static int N = 0; //所有元素個數 static biNodeInOrderArray[MAX_NUM_OF_QUEUE]; double logX(double num,double x) //計算底數為X的對數 { double result; result = log(num)/log(x); return result; } biNode* CreateBiTree(int array[],int n) //建樹,建樹時標記層數,前提是輸入的陣列將按照預設為層次遍歷處理 { biNode *root; if((-1 == array[n]) || (n >= MAX_NUM_OF_QUEUE)) //以-1標記空節點 return NULL; root = (biNode *)malloc(sizeof(biNode)); root->num = array[n]; root->floor = (int)logX(n+1,2)+1; //利用公式:deep=└log2(n+1)┘+1計算出層數,n+1為目前節點個數 root->leftSon = CreateBiTree(array,2*n+1); //遞迴左子樹建樹 root->rightSon = CreateBiTree(array,2*n+2); //遞迴右子樹建樹 return root; } void InOrder(biNode *root) { if(root == NULL) return; InOrder(root->leftSon); if(root->floor < MaxFloor) //獲取小於最大層數的元素個數 NodeCount++; else MaxFloor = root->floor; InOrderArray[N] = *root; N++; InOrder(root->rightSon); } int AdjustFull(int max_deep,int count) //利用公式k層為最低層,則如果是完全二叉樹,則最後一層之上必須是滿二叉樹,2的k次方減1則為之上的全部節點個數 { int num; num = (int)pow(2,max_deep-1)-1; if(count == num) return YES; else return NO; } int AdjustLastFloor() //判斷最後一層是否滿足順序排列 { int i; for(i = 1;i <= (N-NodeCount);i++) { if(InOrderArray[(i-1)*2].floor == MaxFloor) continue; else return NO; } return YES; } int main(void) { int array[MAX_NUM_OF_QUEUE] = {15,1,35,20,3,46,9,7,9,11,21,8,-1,13,-1,-1,-1,-1,-1,-1}; biNode *root; long t1,t2; struct timeval *time1,*time2; time1 = (struct timeval*)malloc(sizeof(struct timeval)); time2 = (struct timeval*)malloc(sizeof(struct timeval)); gettimeofday(time1,NULL); t1 = time1->tv_sec * 1000000 + time1->tv_usec; root = CreateBiTree(array,0); InOrder(root); if(AdjustFull(MaxFloor,NodeCount) == YES) { if(AdjustLastFloor() == YES) printf("Yes\n"); else printf("No\n"); } else printf("No\n"); gettimeofday(time2,NULL); t2 = time2->tv_sec * 1000000 + time2->tv_usec; printf("time:%ld\n",t2-t1); return 0; }
分析
-
.
四.非遞迴中序遍歷
1. 基本思路
由於上述的遞迴實現的演算法在時間效率上有一定問題,則採用非遞迴的方式來實現,解決遞迴中函式呼叫時間消耗大的問題。
-
實現
void
NRInOrder(biNode *root)
{
biNode *p = root;
biNode stack[MAXSIZE];
int top = 0;
if(p == NULL)
return;
while(((p != NULL) || (top != 0)))
{
while(p != NULL)
{
if(top < MAXSIZE-1)
stack[top++] = *p; //[]的優先順序高於=,所以先做top++,再做賦值
else
{
perror("棧溢位");
return;
}
p = p->leftSon;
}
if(top == -1)
return;
else
{
p = &stack[--top];
if(p->floor < MaxFloor) //獲取小於最大層數的元素個數
NodeCount++;
else
MaxFloor = p->floor;
InOrderArray[N] = *p;
N++;
p = p->rightSon;
}
}
}
3.分析
時間複雜度同樣是O(n),但是在效率上比遞迴要高,因為這樣減少了函式呼叫的消耗。
六. 分析
從少量的測試資料測試出來的結果可以看出,在這個數量級(小數量)上,層次遍歷的效率明顯高於深度遍歷,因為層次遍歷經歷了更少的函式呼叫和迴圈比較,儘管時間複雜度都是O(n),但是由於額外的時間開銷導致了深度遍歷的方式更耗時間,而且這個時間將會隨著層數的增加而不斷增加。
但是,因為非遞迴用的是棧的方式實現的,雖然減少了函式呼叫了時間開銷,但是也並沒有實質性的改變受深度制約的影響。所以,可以考慮用非棧的方式實現非遞迴的中序遍歷。