既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;
兄弟节点:具有相同父节点的节点互称为兄弟节点;
树的度:最大的节点的度
节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
树的高度或深度:树中节点的最大层次;
堂兄弟节点:双亲在同一层的节点互为堂兄弟;
节点的祖先:从根到该节点所经分支上的所有节点;
子孙:以某节点为根的子树中任一节点都称为该节点的子孙;
森林:由m(m>0)棵互不相交的树的集合称为森林;
1.2树的表示
- 孩子兄弟表示法
- 双亲表示法
- 孩子表示法
- 孩子兄弟表示法
2.二叉树
2.1二叉树的概念
二叉树是n个有限元素的集合,该集合或者为空、或者由一个称为根(root)的元素及两个不相交的、被分别称为左子树和右子树的二叉树组成,是有序树。当集合为空时,称该二叉树为空二叉树。在二叉树中,一个元素也称作一个结点。
二叉树的特点:
- 每个结点最多有两棵子树,即二叉树不存在度大于2的结点。
- 二叉树的子树有左右之分,其子树的次序不能颠倒。
特殊的二叉树:
- 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。
- 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
2.2二叉树的性质
- 性质1:二叉树的第i层上至多有2i-1(i≥1)个节点 。
- 性质2:深度为h的二叉树中至多含有2h-1个节点 。
- 性质3:若在任意一棵二叉树中,有n0个叶子节点,有n2个度为2的节点,则必有n0=n2+1 。
- 性质4:具有n个节点的完全二叉树深为log2x+1(其中x表示不大于n的最大整数。
- 性质5:若对一棵有n个节点的完全二叉树进行顺序编号(1≤i≤n),那么,对于编号为i(i≥1)的节点:
当i=1时,该节点为根,它无双亲节点 。
当i>1时,该节点的双亲节点的编号为i/2 。
若2i≤n,则有编号为2i的左节点,否则没有左节点 。
若2i+1≤n,则有编号为2i+1的右节点,否则没有右节点
2.3二叉树的存储方式
- 顺序存储:顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。
- 链式存储:二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链。
2.4二叉树的遍历
前序/中序/后序的递归结构遍历:是根据访问结点操作发生位置命名
- NLR:前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。
- LNR:中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
- LRN:后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。
简而言之
方式 | 顺序 |
---|---|
前序遍历 | 根、左、右 |
中序遍历 | 左、根、右 |
后序遍历 | 左、右、根 |
由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。
3.堆
3.1堆的概念
如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆的性质:
1.堆中某个节点的值总是不大于或不小于其父节点的值;
2.堆总是一棵完全二叉树。
3.2堆的实现
Heap.h
#pragma once
typedef int DataType;
//定义一个函数指针类型,用来选择大小堆实现方式
typedef int(\*PCompare)(DataType left, DataType right);
typedef struct Heap
{
DataType \*arr;
int capacity;
int size;
PCompare PCOM; //函数指针变量,来指向所有比较函数
}Heap;
//小堆方式
int Less(DataType child, DataType parent);
//大堆方式
int Greater(DataType child, DataType parent);
//堆的初始化
void HeapInit(Heap \*hp, DataType \*arr, int size,PCompare PCOM);
//堆的插入
void HeapInsert(Heap \*hp, DataType x);
//堆的删除
void HeapDelete(Heap \*hp);
//获取堆顶元素
DataType HeapTop(Heap \*hp);
//获取堆中有效元素个数
int HeapSize(Heap \*hp);
//判空
int HeapEmpty(Heap \*hp);
//堆的销毁
void HeapDestroy(Heap \* hp);
//堆排序
void HeapSort(int arr[],int size);
//TOP-K问题
void PrintTopK(int\* a, int n, int k);
void TestTopk();
void TestHeap();
Heap.c
#include"Heap.h"
#include<stdio.h>
#include<assert.h>
#include<malloc.h>
//小堆方式
int Less(DataType child, DataType parent)
{
return child < parent;
}
//大堆方式
int Greater(DataType child, DataType parent)
{
return child > parent;
}
//交换
void Swap(DataType \*left, DataType \*right)
{
DataType tmp = \*left;
\*left = \*right;
\*right = tmp;
}
//向下调整(大堆)
void AdjustDown(Heap \*hp, int parent)
{
DataType \*arr = hp->arr;
int size = hp->size;
// 默认child标记左孩子,parent的右孩子可能不存在
int child = parent \* 2 + 1;
while (child<size)
{
//在右孩子存在的情况下,找到左右孩子中最小的
if (child + 1 < size && hp->PCOM(arr[child + 1] , arr[child]))
{
child += 1;
}
//检测双亲此时是否满足堆的特性
if (hp->PCOM(arr[child] , arr[parent]))
{
Swap(&arr[child], &arr[parent]);
//较大的双亲向下移动,可能会导致其子树不满足堆的特性,则还需要继续向下调整
parent = child;
child = parent \* 2 + 1;
}
else
{
return;
}
}
}
//向上调整(小堆)
void AdjustUp(Heap \*hp)
{
DataType \*arr = hp->arr;
int child = hp->size - 1;
int parent = (child - 1) / 2;
while (child)
{
if (hp->PCOM(arr[child] , arr[parent]))
{
Swap(&arr[child], &arr[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
return;
}
}
}
//堆的初始化
void HeapInit(Heap \*hp, DataType \*arr, int size, PCompare PCOM)
{
assert(hp);
hp->arr = (DataType \*)malloc(sizeof(DataType)\*size);
if (hp->arr == NULL)
{
assert(0);
return;
}
hp->capacity = size;
//将元素逐个拷进来
for (int i = 0; i < size; i++)
{
hp->arr[i] = arr[i];
}
hp->size = size;
hp->PCOM = PCOM;
//1.找倒数第一个非叶子结点
int LastNotLeafNode = ((size - 1) - 1) / 2;
//2.从该节点开始一直到根结点,逐个往前对每个节点进行向下调整
for (int root = LastNotLeafNode; root >= 0; root--)
{
AdjustDown(hp,root);
}
}
//扩容
void CheckCapacity(Heap \*hp)
{
if (hp->size == hp->capacity)
{
hp->arr = (DataType \*)realloc(hp->arr, sizeof(DataType)\*hp->size \* 2);
if (hp->arr == NULL)
{
assert(0);
return ;
}
hp->capacity \*= 2;
}
}
//堆的插入
void HeapInsert(Heap \*hp, DataType x)
{
CheckCapacity(hp);
//1.将元素先插入有效元素之后
hp->arr[hp->size++] = x;
//2.新元素插入后可能会破坏堆的特性,则还需要对堆进行调整
AdjustUp(hp);
}
//堆的删除
void HeapDelete(Heap \*hp)
{
if(HeapEmpty(hp))
return;
//将堆顶元素与堆中最后一个元素交换
Swap(&hp->arr[0], &hp->arr[hp->size - 1]);
//将堆中有效元素个数减一
hp->size -= 1;
//将堆顶元素向下调整
AdjustDown(hp, 0);
}
//获取堆顶元素
DataType HeapTop(Heap \*hp)
{
assert(!HeapEmpty(hp));
return hp->arr[0];
}
//获取堆中有效元素个数
int HeapSize(Heap \*hp)
{
assert(hp);
return hp->size;
}
//判空
int HeapEmpty(Heap \*hp)
{
assert(hp);
return hp->size == 0;
}
//堆的销毁
void HeapDestroy(Heap \* hp)
{
assert(hp);
if (hp->arr)
{
free(hp->arr);
hp->arr = NULL;
hp->capacity = 0;
hp->size = 0;
}
}
//堆排序的向下调整
![img](https://img-blog.csdnimg.cn/img_convert/7f211f7614ccfb634ad0073416872692.png)
![img](https://img-blog.csdnimg.cn/img_convert/b002caaa6d8e52b6dc1f127d768afe6f.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**
p->arr);
hp->arr = NULL;
hp->capacity = 0;
hp->size = 0;
}
}
//堆排序的向下调整
[外链图片转存中...(img-KHHL7kXB-1715809346571)]
[外链图片转存中...(img-nzhozOw5-1715809346571)]
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**