树的概念:
节点的度:一个节点含有子树的个数.如上图所示A节点的度为6
叶节点/终端节点:度为0的节点,如B,C,H,I,............
父节点:一个节点含有子节点,则这个节点为该子节点的父节点,如A是B的父节点.
子节点:一个节点含有子树的根节点为子节点,如B是A的子节点
树的高度:树的结点的最大层次,如上图树的高度为4
二叉树:
二叉树是由根节点和左右子树构成(不存在度大于2的节点)
如下所示:
满二叉树:
设一个二叉树的层数为K,总结点个数为2^k-1,则称为满二叉树.如下所示
完全二叉树:设树的层数为k,前k-1层是满二叉树,最后一层不满,但是连续的(空节点后面没有非空节点).如下所示:
二叉树的性质:
度为0的叶结点个数为n0,度为2的分支节点个数为n2,则有n0=n2+1;
在上述练习中,n2=199,叶子节点(度为0的节点)=n2+1=199+1=200;
在上述练习中,总节点个数=n0+n1+n2 ,n0=n2+1;所以2n=n0+n1+n2=2*n0+n1-1;n1为0/1,所以n0=n;
最多节点个数:2^(h-1)-1;最少节点个数2^(h-1);
堆:
堆采用的是顺序存储的方式,
小堆(子节点的值大于父结点的值),大堆(父结点的值大于子结点的值)
建堆的时间复杂度为O(N),N为节点个数.
堆的代码实现:
容易出错的函数模块(向上调整)
void AdjustUp(HPDataType* a, int child)//从某个孩子的位置向上调整
{
int parent = (child - 1) / 2;
while (child > 0)
{
if (a[child] < a[parent])//这里是小堆,谁小谁是爹,所以让child<parent。如果是大堆,谁大谁是爹,所以让child>parent
{
Swap(&a[child], &a[parent]);
child = parent;//继续往上走
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
//向上调整是儿子要当爹,所以要把父亲的位置让给儿子,父亲的位置更新
向下调整:
//向下调整(父向下)
void AdjustDown(HPDataType* a, int n, int parent)
{
int child = parent * 2 + 1;//先假设左孩子小
while (child < n)
{
if (child+1<n && a[child + 1] < a[child])
{
++child;//如果右孩子小则+1
}
if (a[child] < a[parent])//因为还要将其设值成小堆,所以要向下走,谁小谁是爹,如果设置成大堆[child] 》 a[parent]
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
//向下调整是父亲当儿子.所以把儿子的位置让给父亲
堆排序:
用向上调整法建堆时间复杂度O(NlogN),向下调整时间复杂度O(N),所以使用向下调整法.
void HeapSort(int* a, int n)//堆排序
{
//对数组建堆 2
for (int i = (n - 1 - 1) / 2; i >= 0; i--)//开始是从0开始的,所以最后坐标是n-1;
{
AdjustDown(a, i);//向下调整建堆
}
int end = n - 1;
while (end > 0) //降序,建小堆
{
Swap(&a[0], &a[end]);
AdjustDown(a, end, 0);
--end;
}
}
TOP-K问题:
先建立k个数的小堆,然后再读取N-K个数,如果比前k个数大则进入堆中,在堆里向下调整,最后出现在堆里的数便是前k个.
void test3()
{
int k;
printf("请输入k>:");
scanf("%d", &k);
//建k个数的小堆
int* kminheap = (int*)malloc(sizeof(int)*k);
if (kminheap == NULL)
{
perror("malloc fail");
return;
}
const char* file = "data.txt";
FILE* fout = fopen(file, "r");
if (fout == NULL)
{
perror("fopen error");
return;
}
//读取前k个数
for (int i = 0; i < k; i++)
{
fscanf(fout, "%d", & kminheap[i]);
}
//建k个数的小堆
for (int i = (k - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(kminheap, k, i);
}
//读取剩下的n-k个数
int x = 0;
while (fscanf(fout, "%d", &x) > 0)
{
if (x > kminheap[0])
{
kminheap[0] = x;
AdjustDown(kminheap, k, 0);
}
}
//打印最大的前k个数
for (int i = 0; i < k; i++)
{
printf("%d ", kminheap[i]);
}
printf("\n");
}
//找前K个最大的数,建一个小堆,如果比堆顶大,进入堆,向下调整
二叉树链式结构:
二叉树的遍历分为前序遍历,中序遍历和后序遍历;
前序遍历:先访问根,再访问左子树,最后访问右子树.
如上图所示,访问顺序为 1 2 3 N N N 4 5 N N 6 N N
中序遍历:先访问左子树,再访问根,最后访问右子树.
如上图所示,访问顺序为 N 3 N 2 N 1 N 5 N 4 N 6 N
后序遍历:先访问左子树,再访问右子树,最后访问根.
如上图所示,访问顺序为 N N 3 N 2 N N 5 N N 6 4 1
访问规则:访问完一边在访问另一边.
二叉树使用的是链式结构.采用的的是递归思想,递归的本质是返回条件和子问题
以计算树的高度为例,如下所示:
二叉树层序遍历:
采用的是队列的思想,当根出队列的时候它的叶子节点便会进队列,从而实现一层一层的遍历.代码如下:
栈和队列 (以及循环队列实现)-CSDN博客(队列的代码在该链接)
void TreeLevelOrder(BTNode* root)
{
Queue q;
QueueInit(&q);
if (root)
QueuePush(&q, root);
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
printf("%d ", front->data);
if(front->left)
QueuePush(&q, front->left);
if (front->right)
QueuePush(&q, front->right);
}
QueueDestroy(&q);
}