自由樹
自由樹是一個連通的,無回路的無向圖。
令G=(V,E)為一個無向圖。下面的表述是等價的。
1) G是自由樹。
2) G中任意兩個頂點由唯一一條簡單路徑得到。
3) G是連通的,但從E中去掉任何邊后得到的圖都是非連通的。
4) G是無回路的,且|E|=|V|-1。
5) G是連通的,且|E|=|V|-1。
6) G是無回路的,但添加任何邊到E中得到的圖包含回路。
二叉樹
在計算機科學中,二叉樹是每個節點最多有兩個子樹的樹結構。通常子樹被稱作“左子樹”(left subtree)和“右子樹”(right subtree)。
二叉樹的每個結點至多只有二棵子樹(不存在度大於2的結點),二叉樹的子樹有左右之分,次序不能顛倒。
二叉樹的第i層至多有2^(i-1)個結點;
深度為k的二叉樹至多有2^k-1個結點;(等比數列1+2+4+…+2^(k-1) = 2^k-1)。
對任何一棵二叉樹T,如果其終端結點數為n0,度為2的結點數為n2,則n0 = n2 + 1。
樹和二叉樹的三個主要差別:
1) 樹的結點個數至少為1,而二叉樹的結點個數可以為0;
2) 樹中結點的最大度數沒有限制,而二叉樹結點的最大度數為2;
3) 樹的結點無左、右之分,而二叉樹的結點有左、右之分。
滿二叉樹
一棵深度為k,且有2^k-1個節點的樹是滿二叉樹。
另一種定義:除了葉結點外每一個結點都有左右子葉且葉子結點都處在最底層的二叉樹。
這兩種定義是等價的。
從樹的外形來看,滿二叉樹是嚴格三角形的,大家記住下面的圖,它就是滿二叉樹的標准形態:

所有內部節點都有兩個子節點,最底一層是葉子節點。
性質:
1) 如果一顆樹深度為h,最大層數為k,且深度與最大層數相同,即k=h;
2) 它的葉子數是: 2^(h-1)
3) 第k層的結點數是: 2^(k-1)
4) 總結點數是: 2^k-1 (2的k次方減一)
5) 總節點數一定是奇數。
6) 樹高:h=log2(n+1)。
完全二叉樹
完全二叉樹是由滿二叉樹而引出來的。對於深度為K的,有n個結點的二叉樹,當且僅當其每一個結點都與深度為K的滿二叉樹中編號從1至n的結點一一對應時稱之為完全二叉樹。
若設二叉樹的深度為h,除第 h 層外,其它各層 (1~h-1) 的結點數都達到最大個數,第h 層所有的結點都連續集中在最左邊,這就是完全二叉樹。
(大家好好理解一下上面兩個定義,是等價的~~)
滿二叉樹一定是完全二叉樹,完全二叉樹不一定是滿二叉樹。
下面是完全二叉樹的基本形態:

完全二叉樹的性質:
1) 深度為k的完全二叉樹,至少有2^(k-1)個節點,至多有2^k-1個節點。
2) 樹高h=log2n + 1。
對滿二叉樹、完全二叉樹總結點及樹高的總結:

堆和棧的區別:
一、堆棧空間分配區別:
1、棧(操作系統):由操作系統自動分配釋放 ,存放函數的參數值,局部變量的值等。其操作方式類似於數據結構中的棧;
2、堆(操作系統): 一般由程序員分配釋放, 若程序員不釋放,程序結束時可能由OS回收,分配方式倒是類似於鏈表。
二、堆棧緩存方式區別:
1、棧使用的是一級緩存, 他們通常都是被調用時處於存儲空間中,調用完畢立即釋放;
2、堆是存放在二級緩存中,生命周期由虛擬機的垃圾回收算法來決定(並不是一旦成為孤兒對象就能被回收)。所以調用這些對象的速度要相對來得低一些。
三、堆棧數據結構區別:
堆(數據結構):堆可以被看成是一棵樹,如:堆排序;
棧(數據結構):一種先進后出的數據結構。
最大堆和最小堆是二叉堆的兩種形式。
最大堆:根結點的鍵值是所有堆結點鍵值中最大者,且每個結點的值都比其孩子的值大。
最小堆:根結點的鍵值是所有堆結點鍵值中最小者,且每個結點的值都比其孩子的值小。

最小堆和最大堆的增刪改相似,其實就是把算法中的大於改為小於,把小於改為大於。
生成最大堆:最大堆通常都是一棵完全二叉樹,因此我們使用數組的形式來存儲最大堆的值,從1號單元開始存儲,因此父結點跟子結點的關系就是兩倍的關系。
堆排序是指利用堆這種數據結構所設計的一種選擇排序算法。堆是一種近似完全二叉樹的結構(通常堆是通過一維數組來實現的),並滿足性質:以最大堆(也叫大根堆、大頂堆)為例,其中父結點的值總是大於它的孩子節點。
我們可以很容易的定義堆排序的過程:
- 由輸入的無序數組構造一個最大堆,作為初始的無序區
- 把堆頂元素(最大值)和堆尾元素互換
- 把堆(無序區)的尺寸縮小1,並調用heapify(A, 0)從新的堆頂元素開始進行堆調整
- 重復步驟2,直到堆的尺寸為1
堆排序的代碼如下:
#include <stdio.h> // 分類 -------------- 內部比較排序 // 數據結構 ---------- 數組 // 最差時間復雜度 ---- O(nlogn) // 最優時間復雜度 ---- O(nlogn) // 平均時間復雜度 ---- O(nlogn) // 所需輔助空間 ------ O(1) // 穩定性 ------------ 不穩定 void Swap(int A[], int i, int j) { int temp = A[i]; A[i] = A[j]; A[j] = temp; } void Heapify(int A[], int i, int size) // 從A[i]向下進行堆調整 { int left_child = 2 * i + 1; // 左孩子索引 int right_child = 2 * i + 2; // 右孩子索引 int max = i; // 選出當前結點與其左右孩子三者之中的最大值 if (left_child < size && A[left_child] > A[max]) max = left_child; if (right_child < size && A[right_child] > A[max]) max = right_child; if (max != i) { Swap(A, i, max); // 把當前結點和它的最大(直接)子節點進行交換 Heapify(A, max, size); // 遞歸調用,繼續從當前結點向下進行堆調整 } } int BuildHeap(int A[], int n) // 建堆,時間復雜度O(n) { int heap_size = n; for (int i = heap_size / 2 - 1; i >= 0; i--) // 從每一個非葉結點開始向下進行堆調整 Heapify(A, i, heap_size); return heap_size; } void HeapSort(int A[], int n) { int heap_size = BuildHeap(A, n); // 建立一個最大堆 while (heap_size > 1) // 堆(無序區)元素個數大於1,未完成排序 { // 將堆頂元素與堆的最后一個元素互換,並從堆中去掉最后一個元素 // 此處交換操作很有可能把后面元素的穩定性打亂,所以堆排序是不穩定的排序算法 Swap(A, 0, --heap_size); Heapify(A, 0, heap_size); // 從新的堆頂元素開始向下進行堆調整,時間復雜度O(logn) } } int main() { int A[] = { 5, 2, 9, 4, 7, 6, 1, 3, 8 };// 從小到大堆排序 int n = sizeof(A) / sizeof(int); HeapSort(A, n); printf("堆排序結果:"); for (int i = 0; i < n; i++) { printf("%d ", A[i]); } printf("\n"); return 0; }
