节点的度:一个节点的子节点
叶子节点: 度为0的节点/没有子节点的节点
非终端节点: 度不为0的节点
父节点:一个节点有分支节点, 这个节点就叫 分支节点的 父节点
子节点:由一个节点分裂的节点 叫 子节点
兄弟节点: 相同的父节点的节点
树的度:一颗树中最大节点的度 就叫树的度
树的高度/深度: 树中节点最大的层次
节点的祖先:从该节点到所经过分支上的所有节点
森林:由m颗树但互不相交的多颗树
二叉树:每一个父节点只有2个子节点
任何一颗二叉树:其度为0的节点个数为N个,度为2的节点个数为M个,则有 N=M+1;
二叉树中的特例:完全二叉树
完全二叉树:高度为H的树中前 h-1层都是满的 最后一层不满,但是从左往右是连续存在节点的
满二叉树:每一层都是满的 上图显示的是就是完全二叉树
我们使用左孩子,右兄弟的方法定义二叉树
typedef char BTDataType;
typedef struct BinaryTreeNode
{
struct BinaryTreeNode* left;//左孩子
struct BinaryTreeNode* right;//右孩子
BTDataType data;
}BTNode;
前序:先遍历根,然后遍历左子树,然后遍历右子树
void PrevOrder(BTNode* root)//前序 二叉树
{
if (root == NULL)
{
printf("NULL ");
return;
}
printf("%c ", root->data);
PrevOrder(root->left);//左子树递归 递归到NULL时返回到上一个递归的调用
PrevOrder(root->right);
}
中序:先遍历左子树,然后遍历根,然后遍历右子树
void InOrder(BTNode* root)//中序
{
if (root == NULL)
{
printf("NULL ");
return;
}
InOrder(root->left);
printf("%c ", root->data);
InOrder(root->right);
}
尾序:先遍历左子树,然后遍历右子树,然后遍历根
void PostOrder(BTNode* root)//尾序
{
if (root == NULL)
{
printf("NULL ");
return;
}
PostOrder(root->left);
PostOrder(root->right);
printf("%c ", root->data);
}
节点个数:算出左子树的个数加上右子树的个数+1(这里的1是根)
int TreeSize(BTNode*root)//计算节点个数
{
return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;//左子树个数+右子树个数+1
}
叶子节点个数:左子树为NULL且右子树为NULL才为叶子节点
int TreeLeafSize(BTNode* root)
{
if (root == NULL)
{
return 0;
}
if (root->left == NULL && root->right == NULL)
{
return 1;
}
return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}
高度:左子树和右子树比大小谁大谁+1
int TreeHeight(BTNode* root)
{
if (root == NULL)
{
return 0;
}
int a = TreeHeight(root->left);
int b = TreeHeight(root->right);
//return fmax(TreeHeight(root->left), TreeHeight(root->right)) + 1;
return a > b ? a + 1 : b + 1;
}
查找节点:
先在左子树找,如果有返回那个节点的,如果没有则在右子树找,右子树有则返回节点,
没有则返回空
(返回的时候要用变量来保存,不然返回时值会被下一次查找覆盖)
BTNode* TreeFind(BTNode* root, BTDataType x)//二叉树查找值为x的节点
{
if (root == NULL)//如果找到了 在左子树 但是还是要走右子树 右子树进入返回来一个空
{
return NULL;
}
if (root->data == x)
{
return root;
}
BTNode* ret = NULL;
ret = TreeFind(root->left, x);
if (ret)
{
return ret;
}
return TreeFind(root->right, x);
}
销毁二叉树:先释放左子树,然后释放右子树,然后释放根
void TreeDestory(BTNode* root)//销毁程序
{
if (root == NULL)
{
return;
}
TreeDestory(root->left);
TreeDestory(root->right);
free(root);
}
堆:非线性表,完全二叉树
堆:底层:
物理结构:数组
逻辑勾结:完全二叉树
小堆:树中任意一个父亲都<=孩子
大堆:树中任意一个父亲都>=孩子
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a;
int size;
int capacity;
}HP;
void Swap(HPDataType* p1, HPDataType* p2);//交换函数
void HeapInit(HP* php);//初始化
void HeapDestory(HP* php);//销毁
void HeapPush(HP* php,HPDataType x);//插入
void HeapPrint(HP* php);//打印
void HeapPop(HP* php);//删除
HPDataType HeapTop(HP* php);//堆顶
bool HeapEmpty(HP* php);//判断为不为空堆
void AdjustUP(HPDataType* a, int child);//向上调整 小堆
void AdjustDown(HPDataType* a, int n, int parent);//向下调整
void HeapInit(HP* php)//初始化
{
assert(php);
php->a = NULL;
php->capacity = php->size = 0;
}
void HeapDestory(HP* php)//销毁
{
assert(php);
free(php->a);
php->a = NULL;
php->capacity = php->size = 0;
}
void Swap(HPDataType* p1, HPDataType* p2)
{
HPDataType tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
完全二叉树相当于一个数组
向上调整:因为堆可以看成一个数组,父节点的下标=(子节点-1)/2,
如果子节点比父节点小那么交换(小堆),大堆则改小于成大于
void AdjustUP(HPDataType* a, int child)//向上调整 小堆
{
int parent = (child - 1) / 2;
while (child>0)
{
if (a[child] < a[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这个右子树不存在那么 则直接输出左子树
//假设做孩子小 如果比右孩子小的话 ++换成右孩子
{
++child;
}
if (a[child] < a[parent])//小孩比父亲大那么交换(大堆)
//小的孩子比 父亲小 那么交换(小堆)
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
插入:先查看空间够不够,不够则扩容空间,然后尾插到堆中
然后进行向上调整,调整到合适位置
void HeapPush(HP* php,HPDataType x)//插入
{
assert(php);
//扩容
if (php->capacity == php->size)
{
int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType)* newCapacity);
if (tmp == NULL)
{
perror("relloc fail");
exit(-1);
}
php->a = tmp;
php->capacity = newCapacity;
}
php->a[php->size] = x;
php->size++;
AdjustUP(php->a, php->size - 1);
}
删除:先让尾节点跟根交换,然后让有效长度–,最后向下调整让整个二叉树成为堆
void HeapPop(HP* php)//删除
{
assert(php);
assert(php->size>0);
Swap(&php->a[0], &php->a[php->size - 1]);
php->size--;
AdjustDown(php->a, php->size, 0);
}
堆顶:
HPDataType HeapTop(HP* php)//堆顶
{
assert(php);
assert(php->size > 0);
return php->a[0];
}
判断为不为空:
bool HeapEmpty(HP* php)
{
assert(php);
return php->size != NULL;
}
打印:
void HeapPrint(HP* php)//打印
{
assert(php);
for (size_t i = 0; i < php->size; i++)
{
printf("%d ", php->a[i]);
}
printf("\n");
}
向上调整(缺点时间复杂度为O(N*logN)):
for (int i = 1; i < n; i++)//向上调整
{
adjustup(a, i);
}
向下调整(时间复杂度为O(N))
int end = n - 1;
for (int i = (end-1)/2; i >=0; i--)//i= 父亲节点
{
adjustdown(a, i, 0);
}
排序完之后 再向下调整(时间复杂度为O(N*logN))
while (end >0)
{
swap(&a[0], &a[end]);
adjustdown( a, end, 0);
end--;
}
void PrintTopk(int* a, int n, int k)
{
if (a == NULL)
{
perror("a fail");
return;
}
int* minheap = (int*)malloc(sizeof(int) * k);
if (minheap == NULL)
{
perror("malloc fail");
return;
}
//保存前k个数据
for (int i = 0; i < k; i++)
{
minheap[i] = a[i];
}
for (int i = (k - 2) / 2; i >= 0; i--)//建小堆
{
AdjustDown(minheap, k, i);
}
/*int end = k - 1;*/
//while (end >0)//向下调整
//{
// Swap(&minheap[0], &minheap[end]);
// AdjustDown( minheap, end, 0);
// end--;
//}
int x = 10;
while (x<=100)
{
if(a[x] > minheap[0])
{
minheap[0] = a[x];
AdjustDown(minheap, k,0);
}
x++;
}
int b = 0;
while (k--)
{
printf("%d ", minheap[k]);
}
}
void TestTopk()
{
int n = 100;
int* a = malloc(sizeof(int) * n);
srand(time(0));
for (int i = 0; i < n; i++)
{
a[i] = rand() % 100;
}
a[9] = 100 + 1;
a[19] = 100 + 2;
a[29] = 100 + 3;
a[39] = 100 + 4;
a[49] = 100 + 5;
a[59] = 100 + 6;
a[69] = 100 + 7;
a[79] = 100 + 8;
a[89] = 100 + 9;
a[99] = 100 + 10;
PrintTopk(a, n, 10);
}
int main()
{
//10亿个数据
//前k个最大的数
//k==100
//数组中存储前100个数建小堆
//如果堆顶的数据比剩下的数据下 则交换 再向下调整
//读完所有数据 ,堆中100个数是最大的前100个
//时间复杂度O(N*logK)
//空间复杂度(K)
TestTopk();
return 0;
}
完全二叉树中的前序就是深度优先遍历
完全二叉树中的层数遍历就是广度优先遍历