目录
1.树的概念与结构
1.1 树的概念
1.2 树的相关概念
1.3 树的表示
typedef int DataType;
struct Node
{
struct Node* firstChild1; // 第一个孩子结点
struct Node* pNextBrother; // 指向其下一个兄弟结点
DataType data; // 结点中的数据域
}
这样我们就可以通过一个结点的brother找到它所有的孩子。
1.4 树在实际中的运用(表示文件系统的目录树结构)
2. 二叉树的概念和结构
2.1 二叉树的概念
2.2 特殊的二叉树
2.3 二叉树的性质
1. 若规定根结点的层数为1,则一棵非空二叉树的第i层上最多有2^(i-1)
/*
* 假设二叉树有N个结点
* 从总结点数角度考虑:N = n0 + n1 + n2 ①
*
* 从边的角度考虑,N个结点的任意二叉树,总共有N-1条边
* 因为二叉树中每个结点都有双亲,根结点没有双亲,每个节点向上与其双亲之间存在一条边
* 因此N个结点的二叉树总共有N-1条边
*
* 因为度为0的结点没有孩子,故度为0的结点不产生边; 度为1的结点只有一个孩子,故每个度为1的结
点* * 产生一条边; 度为2的结点有2个孩子,故每个度为2的结点产生两条边,所以总边数为:
n1+2*n2
* 故从边的角度考虑:N-1 = n1 + 2*n2 ②
* 结合① 和 ②得:n0 + n1 + n2 = n1 + 2*n2 - 1
* 即:n0 = n2 + 1
*/
4.若规定根结点的层数为1,具有n个结点的满二叉树的深度,h=log2(n+1)(ps: log2(n+1)是log以2
为底,n+1为对数)
5.对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有结点从0开始编号,则对于序号为i的结点有:
1.若i>0,i位置结点的双亲序号:(i-1)/2;i=0,i为根结点编号,天无双亲结点
2.若2i+1<n,左孩子序号:2i+1,2i+1>=n否则无左孩子
3.若2i+2<n,右孩子序号:2i+2,2i+2>=n否则无右孩子
2.4 二叉树的存储结构
typedef int BTDataType;
// 二叉链
struct BinaryTreeNode
{
struct BinTreeNode* left; // 指向当前结点左孩子
struct BinTreeNode* right; // 指向当前结点右孩子
BTDataType data; // 当前结点值域
}
// 三叉链
struct BinaryTreeNode
{
struct BinTreeNode* parent; // 指向当前结点的双亲
struct BinTreeNode* left; // 指向当前结点左孩子
struct BinTreeNode* right; // 指向当前结点右孩子
BTDataType data; // 当前结点值域
};
3. 二叉树的顺序结构及实现
3.1 二叉树的顺序结构
3.2 堆的概念及结构
3.3 堆的实现
3.3.1 堆的向下调整算法
3.3.2 堆的创建
堆分为大堆和小堆向上调整算法这里我们不演示了时间复杂度不如向下调整算法下面我们会进行分析的
3.3.3 建堆时间复杂度
这里我们需要注意当我们得到t与h的关系后要把h换成n的表达式因为时间复杂度看的是n个节点调整完需要移动的总步数
向下调整我们可以看到结点越多的层需要调整的次数少;而向上调整结点越多的层数需要调整的次数多因此建堆我们采用向上调整算法。
3.3.4 堆的插入
3.3.4 堆的删除
3.3.5 堆的实现
typedef int HPDataType;
typedef struct Heap
{
HPDataType* _a;
int _size;
int _capacity;
}Heap;
// 堆的构建
void HeapCreate(Heap* hp, HPDataType* a, int n);
// 堆的销毁
void HeapDestory(Heap* hp);
// 堆的插入
void HeapPush(Heap* hp, HPDataType x);
// 堆的删除
void HeapPop(Heap* hp);
// 取堆顶的数据
HPDataType HeapTop(Heap* hp);
// 堆的数据个数
int HeapSize(Heap* hp);
// 堆的判空
int HeapEmpty(Heap* hp);
下面是堆的创建
//向下调整
void AddjustDown(HPDataType* a, int n, int parent)
{
//建立大堆
assert(a);
int child = 2 * parent + 1;
while (child < n)
{
if (child + 1 < n && a[child + 1] > a[child])
{
child++;
}
if (a[parent] < a[child])
{
int tem = a[parent];
a[parent] = a[child];
a[child] = tem;
parent = child;
child = 2 * parent + 1;
}
else
{
break;
}
}
}
// 堆的构建
void HeapCreate(Heap* hp, HPDataType* a, int n)
{
assert(hp);
assert(a);
hp->_capacity = n;
hp->_size = n;
hp->_a = (HPDataType*)malloc(sizeof(HPDataType)*n);
for (int i = (n - 2) / 2; i >= 0; i--)
{
AddjustDown(a, n, i);
}
//接下来把这个数组拷贝一份到堆中
for (int i = 0; i < n; i++)
{
hp->_a[i] = a[i];
}
}
堆的销毁:
// 堆的销毁
void HeapDestory(Heap* hp)
{
assert(hp);
free(hp->_a);
hp->_a = NULL;
hp->_size = 0;
hp->_capacity = 0;
}
//向上调整
AddjustUp(HPDataType* a, int child)
{
int parent = (child - 1) / 2;
while (parent >= 0)
{
if (a[child] > a[parent])
{
int tem = a[parent];
a[parent] = a[child];
a[child] = tem;
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
// 堆的插入
void HeapPush(Heap* hp, HPDataType x)
{
assert(hp);
//插入之前看看是否需要扩容
if (hp->_size == hp->_capacity)
{
int newcapacity = hp->_capacity == 0 ? 4 : 2 * hp->_capacity;
HPDataType* ret = (HPDataType*)realloc(hp->_a, sizeof(HPDataType) * newcapacity);
if (ret == NULL)
{
perror("realloc");
return;
}
hp->_a = ret;
hp->_capacity = newcapacity;
}
hp->_a[(hp->_size)++] = x;
//下面进行向上调整为大堆
AddjustUp(hp->_a, hp->_size - 1);
}
堆的删除:
// 堆的删除
void HeapPop(Heap* hp)
{
assert(hp);
assert(hp->_size);
int tem = hp->_a[0];
hp->_a[0] = hp->_a[hp->_size-1];
hp->_a[hp->_size-1] = tem;
hp->_size--;
AddjustDown(hp->_a, hp->_size, 0);
}
取堆顶的数据
// 取堆顶的数据
HPDataType HeapTop(Heap* hp)
{
assert(hp && hp->_size);
return hp->_a[0];
}
// 堆的数据个数
int HeapSize(Heap* hp)
{
assert(hp);
return hp->_size;
}
// 堆的判空
int HeapEmpty(Heap* hp)
{
assert(hp);
return hp->_size == 0;
}
3.4 堆的应用
3.4.1 堆排序
堆排序就是利用了堆的删除这里以大堆为例,每次把堆顶数据与堆尾交换然后再删除在进行向下调整这样就实现了对数组的排序
这里升序用大堆;降序用小堆。
void HeapSort(HPDataType* a, int n)
{
//建立大堆
for (int i = (n - 2) / 2; i >= 0; i--)
{
AddjustDown(a, n, i);
}
int end = n - 1;
while (end >= 0)
{
int tem = a[0];
a[0] = a[end];
a[end] = tem;
AddjustDown(a, end, 0);
end--;
}
}
3.4.2 Topk问题
void DateCreate()
{
FILE* input = fopen("data.txt", "w");
if (input == NULL)
{
perror("input");
return;
}
for (int i = 0; i < 1000000; i++)
{
int x = rand() % 100 + i;
fprintf(input, "%d\n", x);
}
fclose(input);
input = NULL;
}
int main()
{
srand((unsigned int)time(NULL));
//1.产生数据
DateCreate();
//2.读数据
int n = 0;
printf("请输入你要找几个最小的数:\n");
scanf("%d", &n);
int* arr = (int*)malloc(sizeof(int) * n);
FILE* input = fopen("data.txt", "r");
if (input == NULL)
return;
for (int i = 0; i < n; i++)
{
fscanf(input, "%d\n", &arr[i]);
}
//下面进行建大堆过程找小的数进堆
for (int i = (n - 2) / 2; i >= 0; i--)
{
AddjustDown(arr, n, i);
}
//与堆顶元素比较如果小就进堆
int tem;
while (fscanf(input, "%d", &tem) != EOF)
{
if (tem < arr[0])
{
arr[0] = tem;
AddjustDown(arr, n, 0);
}
}
fclose(input);
input = NULL;
for (int i = 0; i < n; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
4. 二叉树链式结构的实现
4.1 二叉树的创建
首先是树节点的定义
typedef char BTDataType;
typedef struct BinaryTreeNode
{
BTDataType data;
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
}BTNode;
// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi)
{
if (*pi >= n || a[(*pi)] == '#')
{
(*pi)++;
return NULL;
}
BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
newnode->data = a[(*pi)++];
newnode->left = BinaryTreeCreate(a, n, pi);
newnode->right = BinaryTreeCreate(a, n, pi);
return newnode;
}
这里创建一个树的方法就是把该树的前序写成数组按照前序构建树
n是数组元素的个数,pi就是元素下标的地址
4.2 二叉树的遍历
// 二叉树前序遍历
void BinaryTreePrevOrder(BTNode* root)
{
if (root == NULL)
{
printf("# ");
return;
}
printf("%c ", root->data);
BinaryTreePrevOrder(root->left);
BinaryTreePrevOrder(root->right);
}
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{
if (root == NULL)
{
printf("Null ");
return;
}
BinaryTreeInOrder(root->left);
printf("%d ", root->data);
BinaryTreeInOrder(root->right);
}
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{
if (root == NULL)
{
printf("Null ");
return;
}
BinaryTreePostOrder(root->left);
BinaryTreePostOrder(root->right);
printf("%d ", root->data);
}
// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
//大致思路就是把非空结点入队出队时把他的孩子全部入队
//直到队列没有树的结点
Queue queue;
QueueInit(&queue);
if (root == NULL)
return;
QueuePush(&queue, root);
while (!QueueEmpty(&queue))
{
BTNode* top = QueueFront(&queue);
printf("%c ", top->data);
QueuePop(&queue);
if(top->left)
QueuePush(&queue, top->left);
if(top->right)
QueuePush(&queue, top->right);
}
QueueDestroy(&queue);
}
在层序遍历这里我们需要用到队列把非空节点入队出队时让该节点的孩子节点入队知道队列为空
这里我们要知道队列里面的元素类型是指向树节点的指针。
4.3 二叉树的节点数以及高度
// 二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
return root == NULL?0: BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root)
{
if (root == NULL)
{
return 0;
}
if (root->left == NULL && root->right == NULL)
{
return 1;
}
return BinaryTreeLeafSize(root->left)+BinaryTreeLeafSize(root->right);
}
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
if (root == NULL)
{
return 0;
}
if (k == 1 || k == 0)
{
return k;
}
return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}
4.4 二叉树的节点查找
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
if (root == NULL)
{
return NULL;
}
if (root->data == x)
{
return root;
}
BTNode* ret = BinaryTreeFind(root->left, x);
if (ret)
{
return ret;
}
return BinaryTreeFind(root->right, x);
}
4.5 判断是否为完全二叉树
//判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root)
{
Queue q;
QueueInit(&q);
if (root == NULL)
{
return false;
}
QueuePush(&q, root);
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
if (front == NULL)
{
break;
}
QueuePush(&q, front->left);
QueuePush(&q, front->right);
}
while (!QueueEmpty(&q))
{
BTNode* n1 = QueueFront(&q);
QueuePop(&q);
if (n1 != NULL)
{
return false;
}
}
QueueDestroy(&q);
return true;
}
这里的思路就是把空树也入队列当遇到第一个空节点停止让后便利该队列看看是否有非空节点
如果没有就是完全二叉树有一个就不是完全二叉树。当我们遇到第一个空节点时,说明前面的非空节点已经出队列了,也就是前面非空节点的孩子都入队了所以一定可以判断出是否为完全二叉树。
4.6 二叉树的销毁
// 二叉树销毁 rrse
void BinaryTreeDestory(BTNode** root)
{
//*root是指向树节点的一个指针
if ((*root) == NULL)
{
return;
}
BinaryTreeDestory(&((*root)->left));
BinaryTreeDestory(&((*root)->right));
free(*root);
*root = NULL;
}
二叉树的销毁这里我们传入的是二级指针因为我们要改变根节点指针的指向,如果你传入一级指针就要在调用该函数后在手动置为NULL;这里我们用的是后续删除,如果用前序我们要先保存左右字数节点的指针在删掉根节点,后续删除显然更简单。