【苏瞳】C语言+堆排序+选择排序

目录

堆排序

选择排序


堆排序

堆的逻辑结构是一颗完全二叉树 (想象出来的)

堆的物理结构的一个数组(真实内存中存放的)  ->  通过下标访问父子节点关系

物理结构图如下:

逻辑结构图如下:

通过上面我们可以观察,总结这两种图的联系

即: leftchild=parent * 2 +1;

        rightchild=parent * 2 + 2;

        parent=(child - 1) / 2;

(这里的parent,child代表的是在数组中的下标)

学习了上面的结构,下面我们说堆的分类 :最大堆(大顶堆),最小堆(小顶堆)

最大堆要求:树中所以的父亲都大于等于孩子

最小堆要求:树中所以的父亲都小于等于孩子

大堆示意图:

小堆示意图:

那么我们如何使一个无序数组变成一个大或者小堆呢?(下面我们以建大堆为例子)

即:向下调整算法(AjustDown)

向下调整算法有个前提 :就是左右子数必须是大堆,才能使用

例如下图:

上图6节点的左右子数已经是大堆了,但最上面一层不是,这时我们可以用向下调整算法

从根节点开始,选出左右孩子中大的,跟父亲比较,如果父亲小,就交换,然后再继续往下调,调到叶子节点就终止

代码如下,注释详细:

//向下调整算法(使用前提:左右子树必须已经是大堆或者小堆)
void AjustDown(int *arr,int Size,int root)//这里用建大堆做例子
{
	int parent=root;
	int child=2*parent+1; //先默认左孩子大
	while(child < Size)
	{
		if(child+1 < Size && arr[child+1] > arr[child])//child+1 < Size防止只有一个孩子越界
		{
			child=child+1;
		}
		//选孩子中大的和父亲交换
		if(arr[child] > arr[parent])
		{
			Swap(&arr[child],&arr[parent]);
			parent=child;
			child=2*parent+1;
		}
		else//如果父亲已经比两个孩子都大已经满足条件了
		{
			break;
		}
	}
}

这时又有小伙伴想到了,如果左右子数不是大堆怎么办?

答:我们反着来调不就行了嘛,从倒数第一个叶子节点开始调

我们发现叶子节点只有一个值也不需要调,所以最终,我们可以从最后一个非叶子节点开始调节

如何找到最后一个非叶子节点呢? -> 最后一个叶子节点的父亲  -> 下标为:(Size-1 -1)/2

代码如下:(Size为数组大小)

	for(int i=(Size-1-1)/2; i>=0; i--)
	{
		AjustDown(arr,Size,i);
	}

最后我们然后利用大堆来实现排序呢?

我们发现大堆的第一个是最大的。

只需把第一个交换到最后,然后Size--,重新建一次就行了,循环起来

所以升序需要建大堆。

如果建小堆的话,每次取第一个为最小值,然后第一个不变,从第二个开始重新建堆

这样堆的原始结构都被改变了,建小堆不是不行,是没有了效率优势

代码如下:

	int end=Size-1;
	while(end > 0)
	{
		Swap(&arr[0],&arr[end]);
		AjustDown(arr,end,0);
		end--;
	}

最后整体代码如下,注释详细:

#include<stdio.h>

//堆排序 时间复杂度:O(N*logN)
void Swap(int *a,int *b)
{
	int temp=*a;
	*a=*b;
	*b=temp;
}

//向下调整算法(使用前提:左右子树必须已经是大堆或者小堆)
void AjustDown(int *arr,int Size,int root)//这里用建大堆做例子
{
	int parent=root;
	int child=2*parent+1; //先默认左孩子大
	while(child < Size)
	{
		if(child+1 < Size && arr[child+1] > arr[child])//child+1 < Size防止只有一个孩子越界
		{
			child=child+1;
		}
		//选孩子中大的和父亲交换
		if(arr[child] > arr[parent])
		{
			Swap(&arr[child],&arr[parent]);
			parent=child;
			child=2*parent+1;
		}
		else//如果父亲已经比两个孩子都大已经满足条件了
		{
			break;
		}
	}
}

void HeapSort(int *arr,int Size)
{
	//建堆时间复杂度O(N)
	//因为向下调整算法前提,所以我们需要从后向前调整,即从最后一棵非叶子节点开始调(叶子不需要调)
	for(int i=(Size-1-1)/2; i>=0; i--)//最后一棵非叶子节点 -> 最后一个节点的父亲
	{
		AjustDown(arr,Size,i);
	}

	int end=Size-1;
	while(end > 0)
	{
		Swap(&arr[0],&arr[end]);
		AjustDown(arr,end,0);
		end--;
	}
}

int main()
{
	int arr[]={5,2,1,4,3,6,9,8,7,0};
    HeapSort(arr,10);
	for(int i=0;i<10;i++)
	{
		printf("%d ",arr[i]);
	} 
	return 0;
}


选择排序

选择排序(升序):每次选择最大的放到后面 / 取最小的放到前面

时间复杂度O(N^2),这个排序几乎是最low的了,最好最坏时间复杂度都是O(N^2)

平常我们见到的都是每次选择一个,下面我们学习一下优化一点的版本,优化后依然是O(N^2),每次选择两个数,把最大的和最后一个数交换,把最小的到前面。

但如果每次选两个的话,右可能出现bug,例如 9  2  -1  2  3  0,就会出现-1被排在最后

原因是什么呢?看图

我们来分析一下:

1.选出最大值和最小值

2.首先执行的是mini和begin位置互换

3.继续进行maxi和end互换,但当maxi和begin重叠的时候,第二步进行互换的时候maxi的值已经不是最大的数了,被交换到了mini位置,bug就出现在这里,我们只需要修正一下maxi就OK了

#include<stdio.h>

void Swap(int *a,int* b)
{
	int temp=*a;
	*a=*b;
	*b=temp;
}

//直接选择排序(这个是优化后的一次选两个)
//几乎最差的排序,因为最好最坏时间复杂度都是O(N^2)
void SelectSort(int *arr,int Size)
{
	int bagin=0;
	int end=Size-1;
	while(bagin < end)
	{
		int mini=bagin,maxi=bagin;
		for(int i=bagin; i<=end; i++)
		{
			if(arr[i] < arr[mini])
			{
				mini=i;
			}
			
			if(arr[i] > arr[maxi])
			{
				maxi=i;
			}
		}
		Swap(&arr[mini],&arr[bagin]);
		//如果bagin和maxi重叠,需要修正一下maxi的位置
		if(bagin==maxi)
		{
			maxi=mini;
		}
		Swap(&arr[maxi],&arr[end]);
		
		bagin++;
		end--;
	}
}

int main()
{
	int arr[]={9,3,5,2,7,8,6,-1,4,0};
	SelectSort(arr,10);
	for(int i=0;i<10;i++)
	{
		printf("%d ",arr[i]);
	} 
	return 0;
}

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值