目录
一、堆排序
(1)方式一
思路:建立堆,然后获取堆顶元素,再插入数组,在进行堆顶与最后一个元素交换,删除最后一个元素,从堆顶向下调整找到次大或次小的元素,依次重复
//堆排序方式一:
void HeapSort(int* arr,int n)
{
HP hp;
HeapInit(&hp);
int i = 0;
//排成升序,建立一个n个元素的小堆,每次取堆顶的元素,堆顶的元素每次都是最小的,然后删除堆顶元素,之后就是次小的在堆顶
for (i = 0;i < n;i++)
{
HeapPush(&hp,arr[i]);
}
//获取堆顶元素放到arr数组中
for (i = 0;i < n;i++)
{
arr[i] = HeapTop(&hp);
//将堆顶元素删除
HeapPop(&hp);
}
HeapDestroy(&hp);
}
(2)方式二
分析:升序,建大堆,选出最大的数,最大的数跟最后一个数交换,把最后一个数不看做堆里面,进行向下调整,就可以选出次小的数(logn),以此类推,重复上面的过程。
降序则建小堆,选出最小的数,最小的数与最后一个数交换,把最后一个数不看做对立面,进行向下调整,以此重复即可。
//堆排序方式二:直接操控a数组,直接把a建成堆,要求空间复杂度为o(1)
void HeapSort(int* arr, int n)
{
//方式1:
//将下标为0的数当作一个堆,将a构成一个小堆,依次加入后面的数
//int i = 0;
//for (i = 1;i < n;i++)
//{
// AdujustUp(arr, i);//i是控制加入的下标
//}
//方式2:向下调整算法的前提是左右子树都是小堆,
//叶子结点所在的子树不需要调,所以倒着走第一个非叶子节点(最后一个结点的父亲)的子树
int i = n - 1; //o(n)
for (i = (i - 1) / 2;i >= 0;i--)
{
AdjustDown(arr,n,i);
}
//升序,建大堆 o(n*logn)
for (int j = n - 1;j > 0;j--)
{
Swap(&arr[0],&arr[j]);
AdjustDown(arr,j,0);
}
}
二、建堆的时间复杂度
三、二叉树的遍历
(1)前序
代码:
// 二叉树前序遍历
void PreOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
printf("%c ",root->data);
PreOrder(root->left);
PreOrder(root->right);
}
(2)中序
// 二叉树中序遍历
void InOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
InOrder(root->left);
printf("%c ", root->data);
InOrder(root->right);
}
(3)后序
图如前序和中序基本一样
代码:
//二叉树后序遍历
void PostOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
PostOrder(root->left);
PostOrder(root->right);
printf("%c ",root->data);
}
(4)计算二叉树的节点个数(图为上面的二叉树)
方法一:遍历
// 二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
//思路一,遍历
if (root == NULL)
{
return;
}
int count = 0;
++count;
BinaryTreeSize(root->left);
BinaryTreeSize(root->right);
return count;
}
此时的结果为1,为什么呢?
因为递归是在栈中创建的,每次调用都会创建一个新的栈,函数结束,那么里面的数据就会被销毁,而count是一个局部变量,所以每次都被销毁了,所以count为1。
那么我们加一个static(在静态区创建count,每次的count不会被销毁)有用吗?
答案没有错,但是是有些问题的,比如再计算一次它的个数,count每次都没有销毁,就变为了12
所以方法1还是有些问题的,但是也是一种好的思路。
方法二:传计数变量的地址
//二叉树节点个数
void BinaryTreeSize(BTNode* root,int* pn)
{
//思路二:传计数变量的地址,并不会随着栈的销毁而销毁
if (root == NULL)
{
return;
}
++(*pn);
BinaryTreeSize(root->left,pn);
BinaryTreeSize(root->right,pn);
}
方法三:计算左右子树,分治下去
//思路三:
int BinaryTreeSize(BTNode* root)
{
return root == NULL ? 0 : (BinaryTreeSize(root->left) + BinaryTreeSize(root->right) )+1;
}
(5)求二叉树的叶子结点个数
// 二叉树叶子节点个数
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);
}
(6)第k层结点个数
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
assert(k >= 1);
if (root == NULL)
{
return 0;
}
if (k == 1)
{
return 1;
}
//root不为空,并且k>1
//假如k= 4,那么就相当于root->left的第三层
return BinaryTreeLevelKSize(root->left,k-1) + BinaryTreeLevelKSize(root->right,k-1);
}
(7)计算二叉树深度
//计算二叉树的高度
int BinaryTreeDepth(BTNode* root)
{
if (root == NULL)
{
return 0;
}
//栈开辟的太多,比较之后又重新计算了左右子树的高度,所以不提倡
//return BinaryTreeDepth(root->left) > BinaryTreeDepth(root->right) ? (BinaryTreeDepth(root->left) + 1) : (BinaryTreeDepth(root->right) + 1);
//所以最好计算左子树高度和右子树高度之后保存一下,不需要在重新计算
int LeftTreeDepth = BinaryTreeDepth(root->left);
int RightTreeDepth = BinaryTreeDepth(root->right);
return LeftTreeDepth > RightTreeDepth ? LeftTreeDepth + 1 : RightTreeDepth + 1;
}
(8) 二叉树查找值为x的结点
思路:
从根节点找,没找到,到左子树找,左子树没找到,再往左子树的左子树找,直到左子树为NULL,再到右子树去找,依次重复,找到了返回给上一层依次重复
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, DataType x)
{
if (root == NULL)
{
return NULL;
}
//根是不是x
if (root->data == x)
{
return root;
}
//没找到递归到左子树和右子树去找
BTNode* left = BinaryTreeFind(root->left, x);//下一层递归左子树找到了,返回的是这里,是向上一层一层往回返的
if (left != NULL)//不为空就表示找到了,因为没找到返回的是空
{
return left;
}
BTNode* right = BinaryTreeFind(root->right, x);
if (right != NULL)
{
return right;
}
return NULL;
}
(9)检查是否是完全二叉树
思路: 如果当前节点为空,则后面全为空才为完全二叉树,如果当前节点不为空,则将它的孩子插入队列。
代码:
bool BinaryTreeComplete(BTNode* root)
{
Queue queue;
QueueInit(&queue);
QueuePush(&queue,root);
while (!QueueEmpty(&queue))
{
BTNode* front = QueueFront(&queue);
QueuePop(&queue);
if (front == NULL )
{
break;
}
else
{
QueuePush(&queue,front->left);
QueuePush(&queue, front->right);
}
}
//遇到空以后,检查队列中剩下的节点
//1.剩下的全是空,则是完全二叉树
//2.剩下的存在非空,则不是完全二叉树
while (!QueueEmpty(&queue))
{
BTNode* front = QueueFront(&queue);
QueuePop(&queue);
if (front != NULL)
{
QueueDestroy(&queue);
return false;
}
}
QueueDestroy(&queue);
return true;
}
(10) 二叉树的销毁
void BinaryTreeDestroy(BTNode* root)
{
//运用后序的方式,因为如果先把根销毁就找不到左右根了
if (root == NULL)
{
return;
}
BinaryTreeDestroy(root->left);
BinaryTreeDestroy(root->right);
free(root);
}
四、层序遍历
队列中存放的是二叉树的节点
代码:
//层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
//队列里面放的是结点的指针,因为这样才能拿到它的孩子的地址
if (root == NULL)
{
return;
}
Queue s1;
QueueInit(&s1);
QueuePush(&s1,root);
while (!QueueEmpty(&s1))
{
BTNode* front = QueueFront(&s1);
QueuePop(&s1);
//打印出来的这个节点
printf("%c ",front->data);
//将孩子带入
if (front->left != NULL )
{
QueuePush(&s1,front->left);
}
if (front->right != NULL)
{
QueuePush(&s1,front->right);
}
}
printf("\n");
QueueDestroy(&s1);
}
运行结果:
五、二叉树遍历
思路:按照前序建立二叉树,然后进行中序遍历
#include<stdio.h>
#include<stdlib.h>
struct TreeNode
{
struct TreeNode* left;
struct TreeNode* right;
char val;
};
struct TreeNode* CreateTree(char* arr, int* i)
{
if (arr[(*i)] == '#')
{
(*i)++;
return NULL;
}
//建立节点
struct TreeNode* root = (struct TreeNode*)malloc(sizeof(struct TreeNode));
root->val = arr[(*i)++];
root->left = CreateTree(arr, i);
root->right = CreateTree(arr, i);
return root;
}
void InOrder(struct TreeNode* root)
{
if (root == NULL)
return;
InOrder(root->left);
printf("%c ", root->val);
InOrder(root->right);
}
int main()
{
char arr[100];
scanf("%s",arr);
{
int i = 0;
struct TreeNode* root = CreateTree(arr, &i);
InOrder(root);
}
return 0;
}
运行结果: