Day1《啊哈!算法》树

树的特性:
1.一棵树中的任意两个结点有且仅有唯一的一个路径联通
2.一棵树如果有n个结点,那么它一定恰好有n-1条边
3.在一棵树中加一条边将会构成一个回路。
树:任意两个节点间有且仅有一条路径的无向图
叶节点:无子节点
根结点:无父节点
内部节点:既不是根结点,也不是叶节点
深度:从根到该结点的层数
二叉树:每个节点最多有两个儿子(子树)
定义:二叉树要么为空,要么由根结点、左子树和右子树构成,左子树和右子树分别是一颗二叉树
满二叉树:每个内部结点都有两个儿子(严格定义:深度为h且有2^h-1个结点)
完全二叉树:设二叉树的高度为h,除第h层外,其他各层的节点数都达到最大个数,第h层从右到左
连续缺若干结点

堆——特殊的完全二叉树
最小堆——所有父节点都比子节点小(类比得出最大堆)
解决问题1:找出最小数并删除,添加新数,重新找出最小数并删除
思路:把这堆数字按照最小堆的要求放入一颗完全二叉树
删除堆顶的数,将新增的数放到堆顶
不断向下调整,与子节点比较大小,选择较小的与之交换,不断调整至符合最小堆特性为止

void swap(int a,int b)
{
	int temp;
	temp=h[a];
	h[a]=h[b];
	h[b]=temp;
}
void siftdown(int i)//传入一个需要向下调整的结点编号i,这里传入1,即从堆的顶点开始向下调整
{
	int t,flag=0;//flag用来标记是否需要继续向下调整
	//当i结点有儿子(其实是至少有左儿子的情况下)并且需要继续调整的时候,循环执行
	while(i*2<=n&& flag==0)
	{
		//首先判断它和左儿子的关系,并用t记录值较小的结点编号
		if(h[i]>h[i*2])
			t=i*2;
		else
			t=i;
		//如果它有右儿子,在对右儿子进行讨论
		if(i*2+1<=n)
		{
			//如果右儿子的值更小,,更新较小的结点编号
			if(h[t]>h[i*2+1])
				t=i*2+1;
		}
		//如果发现最小的结点编号不能是自己,说明结点中有比父节点更小的
		if(t!=i)
		{
			swap(t,i);//交换它们
			i=t;//更新i为刚才和它交换的儿子结点的编号
		}
		else
			flag=1;//否则说明当前的父节点已经比两个子节点小了,无需调整。
	}
}

问题2:如果只是想新增一个值呢?,
思路:直接将新元素插入到末尾,然后向上调整

int h[14];
void swap(int a,int b)
{
	int temp;
	temp=h[a];
	h[a]=h[b];
	h[b]=temp;
}
void siftup(int i)
{
	int flag=0;
	if(i==1) return;
	while(i!=1&&flag==0)
	{
		if(h[i]<h[i/2])
			swap(i,i/2);
		else
			flag=1;
		i=i/2;
	}
}

问题3:如何建立这个堆?
思路:第一种:从空的堆开始,依次往堆中插入每一个元素,直到所有数都被插入为止,时间复杂度:O(NlogN)
第二种:把n个元素建立一个堆,将这n个结点自顶而下,从左到右编码转换成为一颗完全二叉树
紧接着从最后一个非叶节点(结点编号n/2)开始到根结点(节点编号1),逐个扫描所有的结点,根据需要将当前结点向下调整,直至以当前结点为根结点的子树符合堆的特性
代码实现:
for(i=n/2;i>=1;i–)
siftdown(i);
时间复杂度:O(N)
问题4:堆排序
时间复杂度:O(NlogN)
思路:比如进行从小到大排序,先建立最小堆,然后每次删除顶部元素并将顶部元素输出或者放入一个新的数组中,直到堆为空
最终输出的或者存放在新数组中的数就已经是排序好了的

//删除最大元素
int deletemax()
{
    int t;
    t=h[1];//用一个临时变量记录堆顶点的值
    n--;//堆的元素减少1
    siftdown(1);//向下调整
    return t;//返回之前记录的堆的顶点的最大值
}
建堆以及堆排序的完整代码:
#include<stdio.h>
int h[101];//用来存放堆的数组
int n;//用来存储堆中元素的个数,也就是堆大小
//交换函数,用来交换堆中的两个元素的值
void swap(int x,int y)
{
	int t;
	t=h[x];
	h[x]=h[y];
	h[y]=t;
}
//向下调整函数
void siftdown(int i)//传入一个需要向下调整的结点编号i,这里传入1,即从堆的顶点开始向下调整
{
	int t,flag=0;
	while(i*2<=n&&flag==0)
	{
		if(h[i]>h[i*2])
			t=i*2;
		else
			t=i;
		if(i*2+1<=n)
		{
			if(h[t]>h[i*2+1])
				t=i*2+1;
		}
		if(t!=i)
		{
			swap(t,i);
			i=t;
		}
		else
			flag=1;
	}
}
//建立堆的函数
void creat()
{
	int i;
	//从最后一个非叶节点到第1个结点依次进行向上调整
	for(i=n/2;i>=1;i--)
		siftdown(i);
}
//删除最小元素
int deletemax()
{
	int t;
	t=h[1];//用一个临时变量记录堆顶点的值
	h[1]=h[n];//将堆的最后一个点赋值到堆顶
	n--;//堆的元素减少1
	siftdown(1); //向下调整
	return t;//返回之前记录的堆的顶点的最小值
}
int main()
{
	int i,num;
	//读入要排序的数字的个数
	scanf("%d",&num);
	for(i=1;i<=num;i++)
		scanf("%d",&h[i]);
	n=num;
	//建堆
	creat();
	//删除顶部元素,连续删除n次,其实就是从小到大把数数出来
	for(i=1;i<=num;i++)
		printf("%d ",deletemax());
	getchar();
	getchar();
	return 0;
}

优化的堆排序方法:从小到大排序的时候不建立最小堆而是建立最大堆,最大堆建好后,最大的
元素在h[1],因为我们的需求是从小到大排序,希望最大的放在最后,因此我们将h[1],h[n]交换,此时
h[n]就是数组中最大的元素,注意,交换后还需将h[1]向下调整以保持堆的特性。需要将堆的大小减1.
代码如下:

#include<stdio.h>
int h[101];//用来存放堆的数组
int n;//用来存储堆中元素的个数,也就是堆大小
//交换函数,用来交换堆中的两个元素的值
void swap(int x,int y)
{
	int t;
	t=h[x];
	h[x]=h[y];
	h[y]=t;
}
//向下调整函数
void siftdown(int i)//传入一个需要向下调整的结点编号i,这里传入1,即从堆的顶点开始向下调整
{
	int t,flag=0;
	while(i*2<=n&&flag==0)
	{
		if(h[i]>h[i*2])
			t=i*2;
		else
			t=i;
		if(i*2+1<=n)
		{
			if(h[t]>h[i*2+1])
				t=i*2+1;
		}
		if(t!=i)
		{
			swap(t,i);
			i=t;
		}
		else
			flag=1;
	}
}
//建立堆的函数
void creat()
{
	int i;
	//从最后一个非叶节点到第1个结点依次进行向上调整
	for(i=n/2;i>=1;i--)
		siftdown(i);
}
//堆排序
void heapsort()
{
	while(n>1)
	{
		swap(1,n);
		n--;
		siftdown(1);
	}
}
int main()
{
	int i,num;
	//读入要排序的数字的个数
	scanf("%d",&num);
	for(i=1;i<=num;i++)
		scanf("%d",&h[i]);
	n=num;
	//建堆
	creat();
	//输出
	for(i=1;i<=num;i++)
		printf("%d ",h[i]);

	getchar();
	getchar();
	return 0;
}

感想

学了pytorch的过程中再来学算法,突然就觉得其实算法入门也不是特别难了,不过这个我是不定期更新的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值