二叉树和堆(数据结构)

1.树

定义:一种特殊的数据结构
树的特点:具有多个节点;每个节点都有一个或多个指针;他的形状像一个倒立的树;
在这里插入图片描述

*2.树的元素

结点的度:一个节点包含子树的个数;(及这个节点链接的指针个数)。
叶节点或终端节点:度数为0的节点称做叶节点;
非终端节点或分支节点:度不为0的节点;
根节点:为没有父节点的特殊节点;
双亲节点或父节点:一个指针的上段的元素就是父节点
孩子节点或子节点:一个指针的下端元素为子节点;
兄弟节点:具有相同父节点的节点互相称为兄弟节点;(亲兄弟)
树的度: 一个树中,最大的节点的度为这个树的度;
节点的层次:根的那一层为第一层;子节点的层数为2层,后面一次类推;
树的高度或深度:树中节点的最大层次
堂兄弟节点:双亲在同一层的节点互为堂兄弟;
节点的祖先:节点分支给该节点以上的所有节点
子孙:该节点以下分支的个元素;(必须是情的)

这里层数为什么要从1开始:
在这里插入图片描述
** 数组的下标我为什么从0开始:**
原因:数组下表的本质是:指针加一个下标子再解引用:*(a+i);

3.树的结构

结构本质:父节点和N棵子树,这里的子树的内容也是有父节点和N个子树;因次树是递归定义的;
树的结构小特点:树的子树之间不得相交;(原因:每个子树不能有两个父亲)。

4.树的代码实现

实现方法:这里的实现方法都是使用链表来实现,原因:二叉树除了堆以外都是使用链表来实现)
1.如果明确树的度,那么可以定义(一个元素定义元素应该放的数据和子树的指针;
2.使用顺序表来存储孩子;(这里的是顺序表结构体;这里的数组必须包含盖子来奶的所有地址)
在这里插入图片描述

3.双亲表示法(每个位置只存父的指针或下标)
在这里插入图片描述

4.左孩子 右兄弟表示法
结构体的组成:兄弟指针和子指针和自己的数;、
在这里插入图片描述
普遍示例:
在这里插入图片描述
实践示例
在这里插入图片描述
这里的每一次双击都是由孩子指针来访问孩子;
这里有一个比喻:一个人生了孩子,每一代的孩子大的带小的;
孩子再生孩子;
讲述树可以进行尾删和首插;这里就好比wendios系统的文件的删除和添加;

5.二叉树

5.1基本概念

**1.概念 **:二叉是指每个元素都有两个子指针;
2.分类:满二叉树;完全二叉树

2.1满二叉树:每一层都是满的;

在这里插入图片描述
2.2完全二叉树:最深的层的元素大于1小于最深放满元素的个数;
在这里插入图片描述
3.N行二叉树元素的个数和(使用
等比数列公式可以经计算,错位相乘法):
一个满二叉树的元素个数公式:2^h-1;
一个完全二叉树的元素个数公式:2^(h-1) { 这里是最后一行为1}<x<2 ^(h-1)

计算过程:
3.1满二叉树
在这里插入图片描述
3.2完全二叉树
在这里插入图片描述

5.2二叉树的储存结构

分类:数组结构;链式结构;

5.2.1顺序结构(即数组结构)

1.数组结构的特点:
父子之间的关系:

  1. 父节点的下标找孩子:
    Leftchild=parent*2-1;
    Rightchild=parent*2+2
  2. 孩子找父亲
    Parent=(child-1)/2
    这里的孩子比长辈大;原因是:子辈的数组下表靠后;
    ** 2顺序结构的应用**
    只适用完全二叉树:原因:不完全二叉树会有空间浪费
    在这里插入图片描述
5.2.2 链式结构

二叉链;
三叉链;
在这里插入图片描述

5.3二叉树的应用

1.堆 ————选数(6.讲)
2.搜索树—AVL树—红黑树(后两者是搜索树的改进)
搜索树的特征是:左子树比根小;右子树比根大:
在这里插入图片描述

6.堆

6.1堆的基本概念

1本质
完全二叉树(这里使用数组来实现)
2.分类(有大小顺序来分类)
大堆:树的任意一个父亲都大于或等于孩子
物理结构:数组
逻辑结构:二叉树
小堆:树任意一个父亲都小于等于孩子
物理结构:数组
逻辑结构:二叉树
3.应用 (后边会解释)
1.堆排序——O(N*logN)
2.Topk
3.优先级队列

6.2堆的代码实现

6.2.1堆的代码

这里讲述了
向下调整建堆(这里的需要该元素一下的子树都为大堆货小堆;应用:删除元素后的首元素的调整;
向上调整建堆

//Heap.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int HPDateType;
typedef struct heap
{
	HPDateType* a;
	int size;
	int capacity;
}HP; 
//这里的每个元素不需要组成结构体;原因是这里的每个元素是由一个变量组成;(
// 堆的构建
void HeapInit(HP* php);
// 堆的销毁
void HeapDestroy(HP* php);
// 堆的插入
void HeapPush(HP* php, HPDateType x);
// 堆的删除
void HeapPop(HP* php);
// 取堆顶的数据
HPDateType HeapTop(HP* php);
// 堆的数据个数
int HeapSize(HP* php);
// 堆的判空
bool HeapEmpty(HP* php);
//Heap.c
void HeapInit(HP* php)
{
	php->a = NULL;
	php->size = 0;
	php->capacity = 0;
}
void HeapDestroy(HP* php)
{
	php->a = NULL;
	php->size = 0;
	php->capacity = 0;
}
void Swap(HPDateType* p1, HPDateType*p2)
{
	HPDateType p = *p1;
	*p1 = *p2;
	*p2 = p;
}
void AdjustUp(HPDateType* a, int child)
{
	while (child>0)//这里的判断不要使用父节点原因:这里的子节点为0时;父节点的值为-1/2;不为整数不便使用这里的-1/2的由于取整数所以为0;
	{
		if (a[(child - 1) / 2] > a[child])
		{
			/*HPDateType n = 0;
			n = a[(child - 1) / 2];
			a[(child - 1) / 2] = a[child];
			a[child] = n;
			child = (child - 1) / 2;*/
			Swap(&a[(child - 1) / 2], &a[child]);
			child = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
	

}

void AdjustDown(int* a, int n, int parent)
{
	
	int child = parent * 2 +1;
	while (child<n)//判断child是否为树枝;直到遍历到数组的最后;
	{
		//选择左右孩子中的小的那个,这里先选择左边的为小;
		if(child+1<n&&a[child+1]<a[child])//这里有越界的可能;所以需要判断右子树是否越界;
		{
			++child;
		}
		//判断树元素的大小并进行交换
		if (a[parent] <a[child])
		{
			break;
		}
		else
		{
			Swap(&a[parent],&a[child]);
			parent = child;
			child= parent * 2 + 1;
		}
	}
}
void HeapPush(HP* php, HPDateType x)
{
	assert(php);

		if (php->size == php->capacity)
		{
			int  newcapacity = php->size == 0 ? 4 : 2 * php->capacity;
			HPDateType* tmp = (HPDateType*)realloc(php->a, newcapacity * sizeof(HPDateType));
			if (tmp == NULL)
			{
				perror("realloc失败");//这里tmp指针只是为了判断是否realloc是否成功,即父辈等于子辈;
				return;
			}
			php->a = tmp;
			php->capacity = newcapacity;

		}
		php->a[php->size] = x;//这里的数量正好是下位的下表
		php->size++;


	//排序
	AdjustUp(php->a, php->size - 1);
}
// 堆的删除
void HeapPop(HP* php)
{
	//1.assert 判断堆是否为空
	assert(php);
	assert(!HeapEmpty(php));
	//2.交换
	Swap(&php->a[0], &php->a[php->size - 1]);
	//3.删除
	php->size--;
    //4.调整(向下调整)
	AdjustDown(php->a, php->size, 0);//这里的parent设置为0是从根来排序;

}
// 取堆顶的数据
HPDateType HeapTop(HP* php)
{
	assert(php);
	assert(!HeapEmpty(php));
	return php->a[0];
}
// 堆的数据个数
int HeapSize(HP* php)
{
	assert(php);
	return php->size;
}
// 堆的判空
bool HeapEmpty(HP* php)
{
	assert(php);
	return php->size == 0;
}
//main.c
int main()
{
	HP heap;
	HeapInit(&heap);

	int a[] = { 0,9,2,3,4,5, };
	for (int i = 0; i < sizeof(a)/sizeof(int); i++)
	{
		HeapPush(&heap, a[i]);
	}
	while (!HeapEmpty(&heap))
	{
		int top = HeapTop(&heap);
		printf("%d\n", top);
		HeapPop(&heap);
	}

	return 0;
}
6.2.2堆的时间复杂度**:

由于有添加和删除两个函数;都需要排序是整个元素成为一个树;将连个遍历相加。logN的时间复杂度;logN
在这里插入图片描述
堆的应用
在这里插入图片描述
这里需要与
qsort函数比较
,TOP-K问题是部分进行调试;

6.2.2堆的应用

6.2.2.1堆排序
6.2.2.1.1建堆版

上面的代码没有将数组本身进行改变,而是改变的一个堆内部元素的顺序;下面我将讲述改变数组的方法:

//.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int HPDateType;
typedef struct heap
{
	HPDateType* a;
	int size;
	int capacity;
}HP; 
//这里的每个元素不需要组成结构体;原因是这里的每个元素是由一个变量组成;(
// 堆的构建
void HeapInit(HP* php);
// 堆的销毁
void HeapDestroy(HP* php);
// 堆的插入
void HeapPush(HP* php, HPDateType x);
// 堆的删除
void HeapPop(HP* php);
// 取堆顶的数据
HPDateType HeapTop(HP* php);
// 堆的数据个数
int HeapSize(HP* php);
// 堆的判空
bool HeapEmpty(HP* php);
//.c
#include"Heap.h"

void HeapInit(HP* php)
{
	assert(php);
	php->a = NULL;
	php->size = 0;
	php->capacity = 0;
}
void HeapDestroy(HP* php)
{
	assert(php);
	free(php->a);
	php->a = NULL;
	php->size = 0;
	php->capacity = 0;
}
void Swap(HPDateType* p1, HPDateType*p2)
{
	HPDateType p = *p1;
	*p1 = *p2;
	*p2 = p;
}
void AdjustUp(HPDateType* a, int child)
{
	while (child > 0)//这里的判断不要使用父节点原因:这里的子节点为0时;父节点的值为-1/2;不为整数不便使用这里的-1/2的由于取整数所以为0;
	{
		if (a[(child - 1) / 2] < a[child])
		{
			/*HPDateType n = 0;
			n = a[(child - 1) / 2];
			a[(child - 1) / 2] = a[child];
			a[child] = n;
			child = (child - 1) / 2;*/
			Swap(&a[(child - 1) / 2], &a[child]);
			child = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

void AdjustDown(int* a, int n, int parent)
{
	
	int child = parent * 2 +1;
	while (child<n)//判断child是否为树枝;直到遍历到数组的最后;
	{
		//选择左右孩子中的小的那个,这里先选择左边的为小;
		if(child+1<n&&a[child+1]>a[child])//这里有越界的可能;所以需要判断右子树是否越界;
		{
			++child;
		}
		//判断树元素的大小并进行交换
		if (a[parent] >a[child])
		{
			break;
		}
		else
		{
			Swap(&a[parent],&a[child]);
			parent = child;
			child= parent * 2 +1;
		}
	}
}
void HeapPush(HP* php, HPDateType x)
{
	assert(php);

		if (php->size == php->capacity)
		{
			int  newcapacity = php->size == 0 ? 4 : 2 * php->capacity;
			HPDateType* tmp = (HPDateType*)realloc(php->a, newcapacity * sizeof(HPDateType));
			if (tmp == NULL)
			{
				perror("realloc失败");//这里tmp指针只是为了判断是否realloc是否成功,即父辈等于子辈;
				return;
			}
			php->a = tmp;
			php->capacity = newcapacity;

		}
		php->a[php->size] = x;//这里的数量正好是下位的下表
		php->size++;


	//排序
	AdjustUp(php->a, php->size - 1);
}
// 堆的删除这里删除的是堆的堆顶元素;
void HeapPop(HP* php)
{
	//1.assert 判断堆是否为空
	assert(php);
	assert(!HeapEmpty(php));
	//2.交换
	Swap(&php->a[0], &php->a[php->size - 1]);
	//3.删除
	php->size--;
    //4.调整(向下调整)
	AdjustDown(php->a, php->size, 0);//这里的parent设置为0是从根来排序;

}
// 取堆顶的数据
HPDateType HeapTop(HP* php)
{
	assert(php);
	assert(!HeapEmpty(php));
	return php->a[0];
}
// 堆的数据个数
int HeapSize(HP* php)
{
	assert(php);
	return php->size;
}
// 堆的判空
bool HeapEmpty(HP* php)
{
	assert(php);
	return php->size == 0;
}
//main.c
#include"Heap.h"
//int main()
//{
//	HP heap;
//	HeapInit(&heap);
//
//	int a[] = { 0,9,2,3,4,5, };
//	for (int i = 0; i < sizeof(a)/4; i++)
//	{
//		HeapPush(&heap, a[i]);
//	}
//	while (!HeapEmpty(&heap))
//	{
//		int top = HeapTop(&heap);
//		printf("%d\n", top);
//		HeapPop(&heap);
//	}
//
//	return 0;
//}
void HeapSort(int* a, int n)
{
	//升序--建大堆
	//降序--建立堆
	HP heap;
	HeapInit(&heap);
	for (int i = 0; i < n/*sizeof(a) / 4这里不要使用size(数组名)原因:这里的数组名是数组的首元素地址*/ ; i++)
	{
		HeapPush(&heap, a[i]);
	}
	int i = 0;
	while (!HeapEmpty(&heap))
	{
		int top = HeapTop(&heap);
		a[i++] = top;
		HeapPop(&heap);
	
	}
	HeapDestroy(&heap);

}
int main()
{
	int a[] = { 7,8,3,5,1,9,5,4 };
	HeapSort(a, sizeof(a)/sizeof(int));
	return 0;
}

这种方法可以实现
弊端:
1.前提要建立一个堆;
2.空间复杂度较高;创立了堆
优点
时间复杂度较低;

6.2.2.1.2改数组为堆版

直接将数组改为堆;这样就会减少空间复杂度;
这里的数组排序使用的是堆排序(特点:升序排大堆,降序排小堆)这里使用小堆后首位的为对内最小的,但是这种情况子树无法排序,所有使用将选出来的值放到后面;
原因:这里由于要多次使用堆排序;如果使用使用升序来排小堆;第二次以后的排序会出现错误;堆的父子关系会发生变化;

//,h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int HPDateType;
typedef struct heap
{
	HPDateType* a;
	int size;
	int capacity;
}HP; 
//这里的每个元素不需要组成结构体;原因是这里的每个元素是由一个变量组成;(
// 堆的构建
void HeapInit(HP* php);
// 堆的销毁
void HeapDestroy(HP* php);
// 堆的插入
void HeapPush(HP* php, HPDateType x);
// 堆的删除
void HeapPop(HP* php);
// 取堆顶的数据
HPDateType HeapTop(HP* php);
// 堆的数据个数
int HeapSize(HP* php);
// 堆的判空
bool HeapEmpty(HP* php);
void Swap(HPDateType* p1, HPDateType* p2);
void AdjustDown(int* a, int n, int parent);
void AdjustUp(HPDateType* a, int child);

void HeapInit(HP* php)
{
	assert(php);
	php->a = NULL;
	php->size = 0;
	php->capacity = 0;
}

void HeapDestroy(HP* php)
{
	assert(php);

	free(php->a);
	php->a = NULL;
	php->capacity = php->capacity = 0;
}

void Swap(HPDateType* p1, HPDateType* p2)
{
	HPDateType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

void AdjustUp(HPDateType* a, int child)
{
	int parent = (child - 1) / 2;
	//while (parent >= 0)
	while (child > 0)
	{
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);

			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

void AdjustDown(int* a, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		// 选出左右孩子中小/大的那个
		if (child + 1 < n
			&& a[child + 1] > a[child])
		{
			++child;
		}

		if (a[child] > a[parent])
		{
			Swap(&a[parent], &a[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

// logN
void HeapPush(HP* php, HPDateType x)
{
	assert(php);

	if (php->size == php->capacity)
	{
		int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDateType* tmp = (HPDateType*)realloc(php->a, newCapacity * sizeof(HPDateType));
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}

		php->a = tmp;
		php->capacity = newCapacity;
	}

	php->a[php->size] = x;
	php->size++;

	AdjustUp(php->a, php->size - 1);
}

// logN
void HeapPop(HP* php)
{
	assert(php);
	assert(!HeapEmpty(php));

	Swap(&php->a[0], &php->a[php->size - 1]);
	php->size--;

	AdjustDown(php->a, php->size, 0);
}

HPDateType HeapTop(HP* php)
{
	assert(php);
	assert(!HeapEmpty(php));

	return php->a[0];
}

//
bool HeapEmpty(HP* php)
{
	assert(php);

	return php->size == 0;
}

int HeapSize(HP* php)
{
	assert(php);

	return php->size;
}
``void HeapSort(int* a, int n)
{
	/*升序--建大堆
	降序--建立堆*/
	for (int i = 1; i < n; i++)/*sizeof(a) / 4这里不要使用size(数组名)原因:这里的数组名是数组的首元素地址*/ 
	{
		AdjustUp(a, i);
	}
	int end=n-1;
	while (end>0)
	{    
	
		Swap(&a[0],&a[end]);
		AdjustDown(a, end,0);
		end--;
	}
}
int main()
{
	int a[] = { 7,8,3,5,1,9,5,4 };
	HeapSort(a, sizeof(a)/sizeof(int));
	return 0;
}

这个代码的时间复杂度是NlogN的时间复杂度;
对比N
logN和N^2的时间复杂度的对比;
在这里插入图片描述

注意:
向下排序和向上排序都使用结构体中的元素来代称;而不是直接传结构指针;原因:这里可以在数组中使用;
在这里插入图片描述
在这里插入图片描述
这里的降序建小堆,升序建大堆;升序和降序是形成后的数组顺序;

6.2.2.1.3堆排序的时间复杂度

在这里插入图片描述

向下调整和向上调整的比较
向上调整:
在这里插入图片描述
向下调整
在这里插入图片描述
** 向下调整建堆和向上调整建堆**
向上调账建堆
在这里插入图片描述
向下调整建堆
在这里插入图片描述
时间内复杂度
向上调整建堆:
O(N*logN)
向下调整建堆
O(N)
终结:向下调整舰的时间复杂度低,由此可见实际情况下多用向下调整舰队;

6.2.2.2 Top k问题

解决的问题场景:材料数据过于巨大;想要找到前k个最大/最小的值;
这时使用正常的逻辑无法实现(建立一个大堆,从而找到前k个值)原因是这里遍历一边内存占的过于大;

改进思路
建立一个个数为k的小堆;
再将后N-k个数依次比较和 对顶比较(如果改值比对顶数据大,就将其替换)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值