一目了然——堆与二叉树

前言

本篇文章主要讲解堆的应用以及链式二叉树的实现与应用
通过这篇文章,大家应该能对堆与二叉树更好的理解


1.堆的应用

1.1堆排序

升序 -- 建大堆
降序 -- 建小堆
如果每次都使用建堆选数,整体的时间复杂度是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;
}

1.2时间复杂度

向上调整是从最后一排开始调整,但向下调整没算最后一排,而且,最后一排结点数比较多,差不多占了总结点的一半,所以向上调整的时间复杂度就比较大
在这里插入图片描述
在这里插入图片描述

1.3topk问题

N个数中找出最大或最小的前k个
N是远大于k的

找最大前k个:
排序 O(N*logN)
N个数的大堆,Top/Pop k次 O(N+logN*K)

假设N非常大(100亿),k比较小(100),该如何求解?

  1. 建立k个数的小堆 O(k+(N-k)*logk) 空间效率比较高
  2. 剩下的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;
}

2.二叉树链式结构的实现

注意:
普通二叉树的增删查改并没有意义

如果只用二叉树存储数据,不如用顺序表和链表,那为什么还要学呢?

  1. 为学习后面更复杂的二叉树打基础(搜索二叉树,红黑树,B树,AVL树……)快速搜索数据,他们的增删查找才有意义
  2. 许多有关二叉树的oj算法题,都是出在普通二叉树上

2.1二叉树的遍历

递归结构遍历:
区别在于:访问根的时机

  1. 前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。
    根 左子树 右子树

  2. 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)
    左子树 根 右子树

  3. 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。
    左子树 右子树 根

注意:
任何一棵子树都要被分成根,左子树和右子树,只有空树才是不能再被分割的

在这里插入图片描述

2.2部分代码递归图解

前序递归

在这里插入图片描述

//前序遍历
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);
}

求第k层的结点

在这里插入图片描述

二叉树查找值为x的结点

假设查找的是5,用前序最方便
注意:递归是层层调用,不是一次就返回过去
如果右边没有找到才会去左边找,如果右边找到是直接返回的

2.3完整代码:

//定义一个链式二叉树
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);
}


猜你喜欢

转载自blog.csdn.net/Ll_R_lL/article/details/125105869
今日推荐