堆排序详解

本文详细介绍了两种基于选择的排序算法——直接选择排序和堆排序。直接选择排序的时间复杂度为O(n^2),通过找到最大或最小值并放置到正确位置实现排序。堆排序的时间复杂度为O(nlogn),利用堆这种数据结构进行排序,包括建堆和交换值两个主要步骤。文章提供了C语言的代码实现,帮助读者理解和掌握这两种排序方法。
摘要由CSDN通过智能技术生成

        在常见排序算法中,直接选择排序堆排序都是选择排序的一种,在这篇文章,我将用C语言详细解读直接选择排序和堆排序。

目录

直接选择排序排序

时间复杂度

排序思想

排序实例

代码实现

 

堆排序

时间复杂度

数组与堆的对应关系

排序思想

堆排序步骤

1.生成堆(建堆)

2.交换值

实现代码:


直接选择排序排序

时间复杂度

O (n^2)

排序思想

直接选择排序排序的思想十分简单,常见的思想是找出最大或者最小的元素,将其放到相应位置(升序最大值放末尾,降序最大值放开头),循环找到最大值,直到所有元素都放到相应位置

排序实例

以下是一趟直接选择排序的过程,注意我一趟会找到一个最大值和一个最小值,并且放到相应位置,而不是常见的只找最大值。

 这里存在一个重叠问题,当最大值与最终与相应位置重合时,会产生一些错误,在交换函数Swap中应注意这种情况。

代码实现

//交换两个元素的位置
void Swap(int* a, int* b)
{
	if (*a == *b)//如果交换的两个位置重叠
	{
		return;
	}
	(*a) = (*a) + (*b);
	(*b) = (*a) - (*b);
	(*a) = (*a) - (*b);
}



void SelectSort(int* a, int n)
{
	int left = 0;
	int right = n - 1;
	while (left < right)
	{
		int min = left;
		int max = left;
		for (int i = left; i <= right; i++)//遍历选择最大值和最小值
		{
			if (a[i] > a[max])
			{
				max = i;
			}
			if (a[i] < a[min])
			{
				min = i;
			}
		}
		if (a[max] == a[min])
		{
			return;
		}
		Swap(a + left, a + min);//将最大值和最小值放到相应位置
		Swap(a + right, a + max);
		left++;
		right--;
	}
}

 

堆排序

时间复杂度

O (nlogn)

数组与堆的对应关系

在数组存储堆时,如下

 如元素和其两个孩子之间数组索引值的关系如上

排序思想

堆排序的思想与堆这个结构息息相关,堆是一种特殊的完全二叉树,我们首先来认识一下堆这个结构

大堆:树中的结点大于其左右孩子

小堆:树中的结点小于其左右孩子

堆排序步骤

1.生成堆(建堆)

建堆有两种方法

1.向上调整法

步骤:从第二个元素到最后一个元素循环向上建堆

向上建堆即比较此元素与其父结点的大小,

建大堆时如果此元素大于父节点值,交换此元素和父节点的值

建小堆时如果此元素小于父节点值,交换此元素和父节点的值

代码实现:

void Swap(int* a, int* b)
{
	if (*a == *b)
	{
		return;
	}
	(*a) = (*a) + (*b);
	(*b) = (*a) - (*b);
	(*a) = (*a) - (*b);
}

//向上调整法将数组建大堆
void UpHeap(ElemType* arr, int size)
{
	for (int i = 1; i < size; i++)
	{
		int child = i;
		int parent= (i - 1)/2;
		while (arr[child] > arr[parent])
		{
			Swap(arr + child, arr + parent);
			child = parent;
			if (child==0)
			{
				break;
			}
			parent = (child - 1) / 2;
		}

	}
}


//向上调整法将数组建小堆
void UpHeap2(ElemType* arr, int size)
{
	for (int i = 1; i < size; i++)
	{
		int child = i;
		int parent = (i - 1) / 2;
		while (arr[child] < arr[parent])
		{
			Swap(arr + child, arr + parent);
			child = parent;
			if (child == 0)
			{
				break;
			}
			parent = (child - 1) / 2;
		}

	}
}

 

2.向下调整法(效率大于向上调整法)

步骤:从最后一个非叶子结点往上依次循环向下建堆

经过观察我们可以发现,最后一个非叶子结点的索引一定是n/2-1,n为数组元素个数

向下建堆:寻找孩子结点的较大值与父节点值比较

建大堆时如果此较大值大于父节点值,交换此元素和父节点的值

建小堆时如果此较大值小于父节点值,交换此元素和父节点的值

代码实现:

void Swap(int* a, int* b)
{
	if (*a == *b)
	{
		return;
	}
	(*a) = (*a) + (*b);
	(*b) = (*a) - (*b);
	(*a) = (*a) - (*b);
}




//向下调整法建大堆
void DownHeap(ElemType* arr, int size)
{
	for (int i = size / 2 - 1; i >= 0; i--)//从最后一个非叶子结点开始遍历
	{
		int child = i*2+1;    //左孩子的数组索引
		int parent = i;       //父节点的数组索引
		while (child < size )
		{
			if(arr[child+1]> arr[child] && child + 1 < size)//如果右孩子的值更大
			{
				child = child +1 ;     //较大值孩子改为右孩子
			}
			if (arr[child] > arr[parent])//如果孩子大于父亲
			{
				Swap(arr + child, arr + parent);
				parent = child;
				child= parent * 2 + 1;
			}
			else
			{
				break;
			}
			
		}
	}
}


//向下调整法建小堆
void DownHeap2(ElemType* arr, int size)
{
	for (int i = size / 2 - 1; i >= 0; i--)
	{
		int child = i * 2 + 1;
		int parent = i;
		while (child < size)
		{
			if (arr[child + 1] < arr[child] && child + 1 < size)
			{
				child = child+1;
			}
			if (arr[child] < arr[parent])
			{
				Swap(arr + child, arr + parent);
				parent = child;
				child = parent * 2 + 1;
			}
			else
			{
				break;
			}

		}
	}
}

2.交换值

交换值的步骤十分简单,当时要注重策略,如升序排序时,我们采用建大堆的方法

建立大堆后,我们知道此时根节点一定是最大的,我们交换它和最后一个元素的值,此时根节点到达其最终位置,然后将剩余元素重新生成堆,重复这两个步骤,最终就可以形成升序序列。

到这里有些同学可能会说,为什么不是采用建小堆的方法?

倘若我们采用建小堆的方法,根节点一定是最小值,我们无需再交换它的位置,只需要将剩余元素重新建小堆,当时这个是剩余元素已经完全乱了,重新建堆要消耗大量时间,而在建大堆方法中,只有最后一个元素交换到根节点,意思就是说,只有一个元素不符合堆的规则,即交换到根节点的这个元素重新建堆时间消耗也很小,同理我们可以推断降序排列的情况,所以说升序排序用大堆,降序排序用小堆,可以大大节省时间。

实现代码:


//交换值函数
void Swap(int* a, int* b)
{
	if (*a == *b)
	{
		return;
	}
	(*a) = (*a) + (*b);
	(*b) = (*a) - (*b);
	(*a) = (*a) - (*b);
}



// 堆排序
void AdjustDwon(int* a, int n, int root)//向下调整函数
{
	int parent = root;
	int left = 2 * parent + 1;
	int right = 2 * parent + 2;
	while (left<n)
	{
		int max = 0;

		if (left >= n)
		{
			return;
		}
		if (right >= n)
		{
			max = left;
		}
		else
		{
			max = a[left] > a[right] ? left : right;
		}
		if (a[max] > a[parent])
		{
			Swap(a + max, a + parent);
			parent = max;
		}
		else
		{
			break;
		}
		left = 2 * parent + 1;
		right = 2 * parent + 2;
	}
	
}

void HeapSort(int* a, int n)
{
	for (int i = n / 2 - 1; i >= 0; i--)//先向下建堆
	{
		AdjustDwon(a, n, 0);
	}
	while (n)
	{
		Swap(a, a + n - 1);  //交换最大值和最后一个元素的值
		AdjustDwon(a, n, 0);  //重新向下建堆
		n--;   //最后一个元素位置往前挪动一位

	}
	
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值