算法學習筆記(六) 二叉樹和圖遍歷—深搜 DFS 與廣搜 BFS
阿新 • • 發佈:2018-01-17
創建 mark preorder 第一個 高度 變量初始化 term link 文章
圖的深搜與廣搜
復習下二叉樹、圖的深搜與廣搜。
從圖的遍歷說起。圖的遍歷方法有兩種:深度優先遍歷(Depth First Search), 廣度優先遍歷(Breadth First Search),其經典應用走迷宮、N皇後、二叉樹遍歷等。遍歷即按某種順序訪問“圖”中全部的節點,順序分為:
- 深度優先(優先往深處走),用的數據結構是棧, 主要是遞歸實現。
- 廣度優先(優先走近期的)。用的數據結構是隊列。主要是叠代實現。
對於深搜。因為遞歸往往能夠方便的利用系統棧,不須要自己維護棧。所以通常實現比較簡單。
而對於廣搜。則須要自己維護一個隊列,且因為隊列大小未知,底層存儲的物理結構採用鏈式存儲。
二叉樹概念和性質
二叉樹是每個節點最多有兩個子樹的樹結構,常被用於實現二叉查找樹和二叉堆。
在圖論中,二叉樹定義是一個連通的無環圖。而且每個頂點的度不大於2。二叉樹和樹有非常多類似之處,但並非樹的特殊情形,主要有下面三點主要區別:
- 樹中結點的最大度數沒有限制,而二叉樹結點的最大度數為2。
- 樹的結點無左、右之分,而二叉樹的結點有左、右之分;
- 樹的結點個數至少為1,而二叉樹的結點個數能夠為0
- 結點的度:結點所擁有的子樹的個數稱為該結點的度。二叉樹就僅僅有 0,1。2 三種情況;
- 葉結點:度為 0 的結點稱為葉結點,或者稱為終端結點;
- 分枝結點:度不為 0 的結點稱為分支結點,一棵樹的結點除葉結點外,其余的都是分支結點。
- 結點的層數: 規定樹的根結點的層數為 1,其余結點的層數等於它的雙親結點的層數加 1;
- 樹的深度: 樹中全部結點的最大層數稱為樹的深度;
- 路徑、路徑長度。假設一棵樹的一串結點 n(1), n(2), ?…, n(k) ?有例如以下關系:結點 n(i) 是 n(i+1) 的父結點 (1≤i<k), 就把 n(1),n(2),…,n(k) 稱為一條由 n(1) 至 n(k) 的路徑。
這條路徑的長度是 k-1;
- 滿二叉樹: 在一棵二叉樹中,假設全部分支結點都存在左子樹和右子樹,而且全部葉子結點都在同一層上。
- 全然二叉樹: 深度為 K 的。有 N 個結點的二叉樹,當且僅當其每個結點都與深度為 K 的滿二叉樹中按層序編號從 1 至 N 的結點一一相應;
- 堆是一種全然二叉樹,經常使用的排序算法、Dijkstra 算法、Prim 算法等都經常使用堆優化;
- AVL樹:它是最先發明的自平衡二叉查找樹, 名字來源於其發明者,AVL 樹中不論什麽節點的兩個子樹的高度最大區別為 1, 查找、插入和刪除在平均和最壞情況下都是 O(logn);
- 性質1:在二叉樹的第 i 層上至多有 2i-1 個結點 (i≥1);
- 性質2:深度為 k 的二叉樹上至多含 2k-1 個結點 (k≥1);
- 性質3:對不論什麽一棵二叉樹,若它含有 n0 個葉子結點、n2 個度為 2 的結點,則:n0 = n2+1;
- 性質4:具有 n 個結點的全然二叉樹的深度為 log2n+1;
- 性質5:若對含 n 個結點的全然二叉樹從上到下且從左至右(即按層序)進行 1 至 n 的編號。則對全然二叉樹中隨意一個編號為 i 的結點:
(1) 若 i=1,則該結點是二叉樹的根,無雙親,否則。編號為 i/2 ?的結點為其雙親結點;
(2)?若 2i>n。則該結點無左孩子。否則,編號為 2i 的結點為其左孩子結點。
(3)?若 2i+1>n,則該結點無右孩子結點。否則,編號為 2i+1 的結點為其右孩子結點;
小實驗(C 實現)
這裏首先用一個數組生成一個全然二叉樹(鏈式存儲), 然後深搜用前序遍歷,廣搜借助自己實現的一個隊列(鏈式存儲)來進行。圖例如以下所看到的:
代碼為:
#include <stdio.h> #include <stdlib.h> typedef struct Node { int data; struct Node * left; struct Node * right; } BitNode, *BiTree; typedef BiTree QElemType; // 定義隊列元素類型 typedef struct QNode { QElemType data; // 存放樹節點指針 struct QNode *next; } QNode, *QueuePtr; // 隊列節點和節點指針 typedef struct LinkQueue { QueuePtr front, rear; } LinkQueue; // 隊列前後指針: 隊首。隊尾 /* 初始化隊列 */ int InitQueue(LinkQueue *Q) { QueuePtr s = (QueuePtr) malloc(sizeof(QNode)); if (!s) return 0; Q->front = s; Q->rear = s; return 1; } /* 入隊 */ int EnQueue(LinkQueue *Q, QElemType e) { if (e == NULL) return 0; QueuePtr s = (QueuePtr) malloc(sizeof(QNode)); if (!s) return 0; s->data = e; s->next = NULL; Q->rear->next = s; Q->rear = s; return 1; } /* 出隊 */ int DeQueue(LinkQueue *Q, QElemType *e) { if (Q->front == Q->rear) { return 0; } // 首位指針相等。隊列空,出隊失敗 QueuePtr p; p = Q->front->next; // 頭結點不放數據,其後繼結點作為隊首,出隊 *e = p->data; Q->front->next = p->next; // 隊首指針後移一個節點 if (Q->rear == p) { Q->rear = Q->front; } // 尾指針假設指向的是頭結點後第一個節點,則令其指向隊首指針。 即隊列僅僅有一個數據節點的情況 free(p); return 1; } /* 利用數組 a 創建二叉樹。遞歸的編寫技巧在於設計好函數接口 */ void CreateTree(BiTree *bt, int a[], int len, int index) { if (index > len - 1) return; (*bt) = (BiTree) malloc(sizeof(BitNode)); // 指針變量初始化堆區的內存,c 用 malloc 函數 (*bt)->data = a[index]; (*bt)->left = NULL; // 不能能省略,當其為葉節點時,指針域為 NULL,而且常作為程序推斷條件 (*bt)->right = NULL; CreateTree(&((*bt)->left), a, len, 2 * index + 1); CreateTree(&((*bt)->right), a, len, 2 * index + 2); } /* 前序遍歷二叉樹 , 屬於深度優先遍歷 DFS */ void PreOrderTraverse(BiTree bt) { if (bt == NULL) return; printf("%d ", bt->data); // 操作節點數據 PreOrderTraverse(bt->left); PreOrderTraverse(bt->right); } /* 按層遍歷,即廣度優先遍歷 BFS */ void BFSTraverse(BiTree bt) { LinkQueue *Q = (LinkQueue *) malloc(sizeof(LinkQueue)); BiTree e; InitQueue(Q); EnQueue(Q, bt); while (Q->front != Q->rear) { DeQueue(Q, &e); printf("%d ", e->data); EnQueue(Q, e->left); EnQueue(Q, e->right); } } int main() { int arr[] = { 0, 1, 2, 3, 4, 5, 6}; BiTree root; CreateTree(&root, arr, sizeof(arr) / sizeof(int), 0); printf("PreOrderTraverse: "); PreOrderTraverse(root); printf("\nBFSOrderTravesre: "); BFSTraverse(root); return 0; } /* 程序執行例如以下: PreOrderTraverse: 0 1 3 4 2 5 6 BFSOrderTravesre: 0 1 2 3 4 5 6 */【文章地址為:http://blog.csdn.net/thisinnocence】
算法學習筆記(六) 二叉樹和圖遍歷—深搜 DFS 與廣搜 BFS