、堆 ( Heap )3.1 堆的概念
堆就是将数据以二叉树的顺序存储结构进行实现
且满足 K i < = K 2 ∗ i + 1 {K_i <= K_{2*i+1}} Ki<=K2∗i+1且 K i < = K 2 ∗ i + 2 {K_i <= K_{2*i+2}} Ki<=K2∗i+2(称为小堆)
或 K i > = K 2 ∗ i + 1 {K_i >= K_{2*i+1}} Ki>=K2∗i+1且 K i > = K 2 ∗ i + 2 {K_i >= K_{2*i+2}} Ki>=K2∗i+2(称为大堆)
堆的性质
堆中某个节点的值总是不大于或不小于其父节点的值 (满足大堆或小堆 )
堆总是一棵完全二叉树

3.2 堆的接口
typedefintHeapDatatype;typedefstructHeap{HeapDatatype *a;intcapacity;intsize;}HP;voidHeapInit(HP *php);voidHeapInitArray(HP *php,int*a,intn);voidSwap(HeapDatatype *x1,HeapDatatype *x2);voidAdjustUp(HeapDatatype *a,intchild);voidAdjustDown(HeapDatatype *a,intn,intparent);voidHeapPush(HP *php,HeapDatatype x);voidHeapPop(HP *php);HeapDatatype HeapTop(HP *php);bool HeapEmpty(HP *php);intHeapSize(HP *php);voidHeapDestroy(HP *php);
3.2.1 堆初始化
voidHeapInit(HP *php){assert(php);php->a =(HeapDatatype*)malloc(sizeof(HeapDatatype)*4);if(php->a ==NULL){perror("malloc fail");return;}php->capacity =4;php->size =0;}
3.2.2 堆初始化(以另一个数组来为堆做初始化)
voidHeapInitArray(HP *php,int*a,intn){assert(php);php->a =(HeapDatatype*)malloc(sizeof(HeapDatatype)*n);if(php->a ==NULL){perror("malloc fail");return;}php->capacity =n;php->size =n;for(inti =0;i<n;++i){php->a[i]=a[i];}for(inti =(i-2)/2;i >=0;--i ){AdjustDown(php->a,php->size,i);}}
3.2.3 向上调整
voidAdjustUp(HeapDatatype*a,intchild){intparent =(child-1)/2;while(child >0){if(a[child]>a[parent]){Swap(&a[child],&a[parent]);child =parent;parent =(child-1)/2;}else{break;}}}
3.2.4 向下调整
voidAdjustDown(HeapDatatype*a,intn,intparent){intchild =2*parent +1;while(child <n){if(child +1<n &&a[child]<a[child+1]){child++;}if(a[child]>a[parent]){Swap(&a[child],&a[parent]);parent =child;child =2*parent+1;}else{break;}}}
3.2.5 插入数据
voidHeapPush(HP *php,HeapDatatype x){assert(php);if(php->capacity ==php->size){HeapDatatype*tmp =(HeapDatatype*)realloc(php->a,sizeof(HeapDatatype)*2*php->capacity);if(tmp ==NULL){perror("realloc fail");return;}php->a =tmp;php->capacity *=2;}php->a[php->size++]=x;AdjustUp(php->a,php->size-1);}
3.2.6 删除数据
删除数据的思路

代码实现
voidHeapPop(HP *php){assert(php);Swap(&php->a[0],&php->a[size-1]);php->size--;AdjustDown(php->a,php->size,0);}
3.2.7 取堆顶的数据
HeapDatatype HeapTop(HP *php){assert(php);returnphp->a[0];}
3.2.8 判断堆是否为空
bool HeapEmpty(HP *php){assert(php);returnphp->size ==0;}
3.2.9 取堆中有效数据个数
intHeapSize(HP *php){assert(php);returnphp->size;}
3.2.10 堆的销毁
voidHeapDestroy(HP *php){assert(php);free(php->a);php->a =NULL;php->size =0;php->capacity =0;}
3.3 堆的分析
- 堆的时间复杂度:O( n ) – 利用错位相减法可以得到
- 堆的空间复杂度:取决于malloc的大小 ( 通常是O(n) )
3.4 堆的应用
堆排序
堆排序的两个步骤
建堆
利用堆删除的思想来完成堆排序
代码实现
voidHeapSort(int*a,intn){for(inti =0;i<n;++i){AdjustDown(a,i);}while(n >=0){Swap(&a[0],&a[n-1]);AdjustDown(a,n,0);--n;}}
Top-K 问题
排完序后(这里要排降序),我们就可以取得最大的前 k 个数据
但如果数据量大的话,直接排序就不是那么好的方法,因此,我们可以采用以下方法
建堆
求前 k 个最大的数据 – 建大堆
求前 k 个最小的数据 – 建小堆
用剩余的 N-K 个元素依次与堆顶元素来比较,不满足则替换堆顶元素,将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素
四、p、ul、右指针( 指向右孩子 – rightchild)所组成
而二叉树的延伸 – 红黑树则以三叉链表来表示,在二叉链表的基础上再加上指向双亲节点的指针

在这里我们主要介绍一般的二叉树
三、

在树中,有一个特殊的节点称为根节点(如上图中的 body 节点)
根节点的特色是他没有前驱节点,除了根节点之外的其他节点被分成了 M 个 ( M > 0 ) 互不相交的集合T 1 、div)
叶节点或终端节点:度为零的节点
以上图为例: li、根节点的左子树、根节点的右子树组成
从二叉树的逻辑结构不难发现二叉树的实现是以递归来实现的
而对二叉树的增删查改通常不是通过一般的二叉树实现的,所以在这里我们不讨论二叉树的增删查改,我们会利用二叉树的遍历来了解最一般的二叉树
4.2 二叉树的遍历
二叉树的遍历主要问题有四种
前序遍历:访问顺序 – 根 - 左子树 - 右子树
中序遍历:访问顺序 – 左子树 - 根 - 右子树
后序遍历:访问顺序 – 左子树 - 右子树 - 根
层序遍历:访问顺序 – 依序访问每一层的节点,一层访问完后再访问下一层

层序遍历不仅遍历方式和其他三种不一样,实现方式也与其他三种不一样
4.3 二叉树的遍历(代码实现)
4.3.1 创建节点
因为我们不是以正规的二叉树创建方法实现,所以我们要自己创建二叉树
typedefintBTDatatype;typedefstructBinaryTreeNode{BTDatatype data;structBinaryTreeNode*left;structBinaryTreeNode*right;}BTNode;BTNode*CreateNode(BTDatatype x){BTNode*node =(BTNode*)malloc(sizeof(BTNode));if(node ==NULL){perror("malloc fail");return;}node->data =x;node->left =NULL:node->right =NULL:returnnode;}
4.3.2 创建树
创建节点并创建树
BTNode*CreateTree(){BTNode*node1 =CreateNode(1);BTNode*node2 =CreateNode(2);BTNode*node3 =CreateNode(3);BTNode*node4 =CreateNode(4);BTNode*node5 =CreateNode(5);BTNode*node6 =CreateNode(6);node1->left =node2;node1->right =node3;node2->left =node4;node2->right =node5;node3->right =node6;returnnode1;}
4.3.3 二叉树遍历
voidPreOrder(BTNode*root){if(root ==NULL){printf("NULL ");return;}printf("%d ",root->data);PreOrder(root->left);PreOrder(root->right);}
voidInOrder(BTNode*root){if(root ==NULL){printf("NULL ");return;}InOrder(root->left);printf("%d ",root->data);InOrder(root->right);}
voidPostOrder(BTNode*root){if(root ==NULL){printf("NULL ");return;}PostOrder(root->left);PostOrder(root->right);printf("%D ",root->data);}
4.3.4 二叉树的其他接口
intTreeSize(BTNode*root){if(root ==NULL){return;}intlnum =TreeSize(root->left);intrnum =TreeSize(root->right);returnlnum +rnum +1;}intTreeHeight(BTNode*root){if(root ==NULL){return0;}intlnum =TreeHeight(root->left);intrnum =TreeHeight(root->right);returnlnum >rnum ?lnum +1:rnum +1;}
4.3.4 第k層的節點數
intTreeKLevel(BTNode*root,intk){if(root ==NULL){return0;}if(k ==1){return1;}intlnum =TreeKLevel(root->left,k-1);intrnum =TreeKLevel(root->right,k-1);returnlnum +rnum;}
4.3.5 寻找值为x的节点(如果有多个值为x的节点则返回第一个节点)
BTNode*BinaryTreeFind(BTNode*root,BTDatatype x){if(root ==NULL){returnNULL;}if(root->data ==x){returnroot;}BTNode*lret =BTNBinaryTreeFind(root->left,x);if(lret){returnlret;}BTNode*rret =BinaryTreeFind(root->right,x);if(rret){returnrret;}returnNULL;}
4.3.6 层序遍历 & 判断一棵树是否是完全二叉树
层序遍历
我们前面有说过,层序遍历的实现和前、二叉树的概念
2.1 概念
二叉树的本质就是树,但二叉树有着自己的特殊性质

从图中可以看到两个二叉树的性质
- 二叉树每个节点的度不会大于2
- 二叉树有左右子树之分
二叉树的不同情况

注意:树可以没有节点,此时就是空树
2-2 特殊二叉树
满二叉树 ( 图 a )
一个二叉树每一层的节点数都是该层的最大值,则这个二叉树就是满二叉树
完全二叉树 (图 b )
一个二叉树每一层的节点依照从左至右的顺序,如果有一层的节点不是该层的最大值,则这个二叉树是完全二叉树
注意:满二叉树是一个特殊的完全二叉树

2-3 二叉树的性质
若根节点的层数为1,则第 i 层的最大节点数为 2 i − 1 {2^{i-1}} 2i−1
若根节点的层数为1,则深度为 h 的二叉树最大节点数为 2 h − 1 {2^h - 1} 2h−1
对任何一棵二叉树, 如果度为 0 其叶节点个数为 n 0 {n_0} n0, 度为 2 的分支节点个数为 n 2 {n_2} n2,则有 n 0 = n 2 + 1 {n_0 = n_2 + 1} n0=n2+1
若规定根节点的层数为1,具有 n 个节点的满二叉树的深度,h = l o g 2 ( n + 1 ) {h = log_2(n+1)} h=log2(n+1)
注意这里的 log 是以2为底
对于具有 n 个节点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对
于序号为 i。中、div
兄弟节点:有相同双亲节点的节点
以上图为例:ul的兄弟节点是p和div;li的兄弟节点是li和img
树的度:最大节点的的度就是树的度
以上图为例:最大节点的度是3 (body节点)所以树的度为3
节点的层次:以根节点开始,根为第一层,根节点的子节点为第二层,以此类推
树的高度:就是节点的最大的层次
以上图为例:最大的节点层次是3,所以树的高度是3
堂兄弟节点:双亲在同一层的节点互为堂兄弟
以上图为例 li 和 img 是堂兄弟节点
节点的祖先:从根到该节点所经分支上的所有节点
以上图为例:body是所有节点的祖先
子孙:以某节点为根的子树中任一节点都称为该节点的子孙
以上图为例:除了body节点以外的节点都是body节点的子孙
森林:由 m(m>0)棵互不相交的树的集合称为森林
二、1.2 与树相关的专有名词
节点的度:一个节点的子树个数
以上图为例:body 节点的度为 3 (ul、