自由树
自由树是一个连通的,无回路的无向图。
令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; }