本篇文章主要讲解堆的应用以及链式二叉树的实现与应用
通过这篇文章,大家应该能对堆与二叉树更好的理解
升序 -- 建大堆
降序 -- 建小堆
如果每次都使用建堆选数,整体的时间复杂度是O(N)
利用堆删除思想来进行排序
// 1、你得先写一个Hp数据结构,反而复杂
// 2、有O(N)空间复杂度
//void HeapSort(int* a, int n)
//{
// HP hp;
// HeapInit(&hp);
// for (int i = 0; i < n; ++i)
// {
// HeapPush(&hp, a[i]);
// }
//
// int i = 0;
// while (!HeapEmpty(&hp))
// {
// a[i++] = HeapTop(&hp);
// HeapPop(&hp);
// }
// HeapDestroy(&hp);
//}
// 升序 -- 建大堆
// 降序 -- 建小堆
void HeapSort(int* a, int n)
{
建堆方式1:O(N*logN)
//for (int i = 1; i < n; ++i)
//{
// AdjustUp(a, i);
//}
// 建堆方式2:O(N)
for (int i = (n-1-1)/2; i >= 0; --i)
{
AdjustDwon(a, n, i);
}
// O(N*logN)
int end = n - 1;
while (end > 0)
{
Swap(&a[0], &a[end]);
AdjustDwon(a, end, 0);
--end;
}
}
void TestHeapSort()
{
// 升序打印 -- 小堆
// 降序打印 -- 大堆
/*HP hp;
HeapInit(&hp);
int a[] = { 27, 15, 19, 18, 28, 34, 65, 49, 25, 37 };
for (int i = 0; i < sizeof(a) / sizeof(int); ++i)
{
HeapPush(&hp, a[i]);
}
while (!HeapEmpty(&hp))
{
printf("%d ", HeapTop(&hp));
HeapPop(&hp);
}
printf("\n");*/
int a[] = { 27, 15, 19, 18, 28, 34, 65, 49, 25, 37 };
HeapSort(a, sizeof(a) / sizeof(int));
}
int main()
{
TestHeapSort();
return 0;
}
向上调整是从最后一排开始调整,但向下调整没算最后一排,而且,最后一排结点数比较多,差不多占了总结点的一半,所以向上调整的时间复杂度就比较大
N个数中找出最大或最小的前k个
N是远大于k的
找最大前k个:
排序 O(N*logN)
N个数的大堆,Top/Pop k次 O(N+logN*K)
假设N非常大(100亿),k比较小(100),该如何求解?
- 建立k个数的小堆 O(k+(N-k)*logk) 空间效率比较高
- 剩下的N-k个一次跟堆顶的数据比较,若比堆顶数据大,就替换堆顶数据进堆 走完以后,堆里面的k个数就是最大的前k个
void PrintTopK(int* a, int n, int k)
{
// 1. 建堆--用a中前k个元素建堆
int* kMinHeap = (int*)malloc(sizeof(int)*k);
assert(kMinHeap);
for (int i = 0; i < k; ++i)
{
kMinHeap[i] = a[i];
}
for (int i = (k - 1 - 1) / 2; i >= 0; --i)
{
AdjustDwon(kMinHeap, k, i);
}
// 2. 将剩余n-k个元素依次与堆顶元素交换,不满则则替换
for (int j = k; j < n; ++j)
{
if (a[j] > kMinHeap[0])
{
kMinHeap[0] = a[j];
AdjustDwon(kMinHeap, k, 0);
}
}
for (int i = 0; i < k; ++i)
{
printf("%d ", kMinHeap[i]);
}
printf("\n");
}
void TestTopk()
{
int n = 10000;
int* a = (int*)malloc(sizeof(int)*n);
srand(time(0));
for (int i = 0; i < n; ++i)
{
a[i] = rand() % 1000000;
}
a[5] = 1000000 + 1;
a[1231] = 1000000 + 2;
a[531] = 1000000 + 3;
a[5121] = 1000000 + 4;
a[120] = 1000000 + 5;
a[99] = 1000000 + 6;
a[0] = 1000000 + 7;
a[76] = 1000000 + 8;
a[423] = 1000000 + 9;
a[3144] = 1000000 + 10;
PrintTopK(a, n, 10);
}
int main()
{
TestTopk();
return 0;
}
注意:
普通二叉树的增删查改并没有意义
如果只用二叉树存储数据,不如用顺序表和链表,那为什么还要学呢?
- 为学习后面更复杂的二叉树打基础(搜索二叉树,红黑树,B树,AVL树……)快速搜索数据,他们的增删查找才有意义
- 许多有关二叉树的oj算法题,都是出在普通二叉树上
递归结构遍历:
区别在于:访问根的时机
前序遍历
(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。
根 左子树 右子树
中序遍历
(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)
左子树 根 右子树
后序遍历
(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。
左子树 右子树 根
注意:
任何一棵子树都要被分成根,左子树和右子树,只有空树才是不能再被分割的
//前序遍历
void PreOrder(BTNode* root)
{
if (root == NULL)
{
printf("# ");
return;
}
printf("%d ", root->data);//根
PreOrder(root->left);//左子树
PreOrder(root->right);//右子树
}
//中序
void InOrder(BTNode* root){
if (root == NULL)
{
printf("# ");
return;
}
InOrder(root->left);
printf("%d ", root->data);
InOrder(root->right);
}
假设查找的是5,用前序最方便
注意:递归是层层调用,不是一次就返回过去
如果右边没有找到才会去左边找,如果右边找到是直接返回的
//定义一个链式二叉树
typedef int BTDataType;
typedef struct BinaryTreeNode
{
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
BTDataType data;
}BTNode;
//处理空树
BTNode* BuyNode(BTDataType x)
{
BTNode* node = (BTNode*)malloc(sizeof(BTNode));
assert(node);
node->data = x;
node->left = NULL;
node->right = NULL;
return node;
}
//手搓一个二叉树
BTNode* CreatBinaryTree()
{
BTNode* node1 = BuyNode(1);
BTNode* node2 = BuyNode(2);
BTNode* node3 = BuyNode(3);
BTNode* node4 = BuyNode(4);
BTNode* node5 = BuyNode(5);
BTNode* node6 = BuyNode(6);
node1->left = node2;
node1->right = node4;
node2->left = node3;
node4->left = node5;
node4->right = node6;
return node1;
}
//前序遍历
void PreOrder(BTNode* root)
{
if (root == NULL)
{
printf("# ");
return;
}
printf("%d ", root->data);//根
PreOrder(root->left);//左子树
PreOrder(root->right);//右子树
}
//中序
void InOrder(BTNode* root){
if (root == NULL)
{
printf("# ");
return;
}
InOrder(root->left);
printf("%d ", root->data);
InOrder(root->right);
}
//后序
void PostOrder(BTNode* root){
if (root == NULL){
printf("# ");
return;
}
PostOrder(root->left);
PostOrder(root->right);
printf("%d ", root->data);
}
//计数
//定义全局变量
int count = 0;
void TreeSize1(BTNode* root)
{
if (root == NULL)
{
return;
}
++count;
TreeSize1(root->left);
TreeSize1(root->right);
}
//解决不用置空的问题,用分治的思路
int TreeSize2(BTNode* root)
{
return root == NULL ? 0 :
TreeSize2(root->left) + TreeSize2(root->right) + 1;
}
//求叶子结点数量
//分三步走
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);
}
//求第k层结点数
//转换成子问题:求左子树的第k-1层 + 求右子树的第k-1层
int TreeKLevel(BTNode* root, int k)
{
assert(k >= 1);
if (root == NULL)
return 0;
if (k == 1)
return 1;
return TreeKLevel(root->left, k - 1)
+ TreeKLevel(root->right, k - 1);
}
int main()
{
BTNode* root = CreatBinaryTree();
PreOrder(root);
printf("\n");
InOrder(root);
printf("\n");
PostOrder(root);
printf("\n");
//每次调用之前要置空,防止叠加
count = 0;
TreeSize1(root);
printf("TreeSize:%d\n", count);
count = 0;
TreeSize1(root);
printf("TreeSize:%d\n", count);
printf("TreeSize:%d\n", TreeSize2(root));
printf("TreeSize:%d\n", TreeSize2(root));
printf("LeafSize:%d\n", TreeLeafSize(root));
printf("KLevelSize:%d\n", TreeKLevel(root, 2));
printf("KLevelSize:%d\n", TreeKLevel(root, 3));
printf("KLevelSize:%d\n", TreeKLevel(root, 4));
return 0;
}
// 二叉树查找值为x的结点
BTNode* TreeFind(BTNode* root, BTDataType x)
{
if (root == NULL)
return NULL;
if (root->data == x)
return root;
BTNode* ret1 = TreeFind(root->left, x);
if (ret1)
return ret1;
BTNode* ret2 = TreeFind(root->right, x);
if (ret2)
return ret2;
return NULL;
}
// 二叉树查找值为x的结点
BTNode* TreeFind(BTNode* root, BTDataType x)
{
if (root == NULL)
return NULL;
if (root->data == x)
return root;
BTNode* ret1 = TreeFind(root->left, x);
if (ret1)
return ret1;
BTNode* ret2 = TreeFind(root->right, x);
if (ret2)
return ret2;
return NULL;
}
// 求二叉树深度
//求左子树的深度和右子树的深度,在其中大的那个加一就是总的深度
//后序
int TreeDepth(BTNode* root);
{
if (root == NULL)
{
return 0;
}
int leftDepth = TreeDepth(root->left);
int rightDepth = TreeDepth(root->right);
return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1;
}
//二叉树的销毁
//用后序销毁
//先销毁左子树再销毁右子树最后销毁根结点
void TreeDestroy(BTNode* root)
{
if(root == NULL)
{
return;
}
TreeDestroy(root->left);
TreeDestroy(root->right);
free(root);
}