YAZONG 我的开源

(数据结构)第九讲-排序(9.3 堆排序)

  , ,
0 评论0 浏览

9.3.1 选择排序 (Θ(N^2) )

#include<stdio.h>
/*
排序
*/
int main(void){
	return 0;
}
/*
排序-选择排序-时间复杂度(T = Θ(N^2))
*/
void Selection_sort(ElementType A[], int N){

	for(i = 0; i < N; i++){
		/*
		从A[i]到A[i - 1]中找到最小元,并将其位置赋给MinPosition.
		这里是瓶颈.
		选择排序最简单粗暴的方法:从A[i]到A[i - 1]每次都扫描一遍,把最小元记录到A[i]中去.
		ScanForMin对应的也是一个循环,外头又套了一层循环,那么时间复杂度(T = Θ(N^2)).
		*/
		MinPosition = ScanForMin(A, i, N - 1);
		/*
		将未排序部分的最小元换到有序部分的最后位置.
		有序部分的最后位置就是i这个位置.
		下述的两个元素在大多数情况下不是挨着的,可能跳了很远的距离做了一个交换,那么这一次的交换就是一个好消息,
		我们可能就一次交换就消掉了很多的逆序对,其实对于这个选择排序而言,最坏情况下每次都要换一次,
		最多换N-1次,最后一个元素不用换,交换这部分时间的复杂度是线性的O(N).
		*/
		Swap(A[i], A[MinPosition]);
	}

}

9.3.2 堆排序 (最小元)

堆的元素是从第一个下标为1的元素开始计数的,A[0]是不放任何真元素的,而是放的哨兵,而在排序的算法中,用户并不知道应该在前面留个哨兵元素,用户是从第0个元素就开始存在的,所以堆中的元素也是从0开始记的,这就导致了任何一个结点跟它的孩子结点的下标关系就不同了。

那么在堆排序中,元素下标从0开始,则对于下标为i的元素,左孩子的下标为2i+1,右孩子的下标为2i+2。

(对比5.1和9.3)

源码:堆排序-未优化( O(NLogN) )


/*
排序-堆排序-算法1-未优化
时间复杂度:T(N) = O(NLogN)
需要额外O(N)的空间,并且复制元素需要时间.
*/
void Heap_Sort(ElementType A[], int N){
	/* 线性时间复杂度O(N) */
	BuildHeap(A);
	for(i = 0; i < N;i++){
		/* 
		O(LogN)加上外循环就是O(NLogN) 
		弹出根结点,弹出来之后开辟临时数组TmpA存放数据,要需要把这个临时数组TmpA中的数据倒回A数组中去.
		*/
		TmpA[i] = DeleteMin(A);
	}
	for(i = 0; i < N;i++){
		/* 
		O(N) 
		如果内存是2GB,可以对2GB的数据进行排序,因为要开额外的空间A[i] = TmpA[i],
		导致一次只能排1GB的内容,复制元素也是需要占据空间的。
		*/
		A[i] = TmpA[i];
	}
}

案例:堆调整排序(左孩子下标:2i+1-右孩子下标2i+2)

image.png

(1)调整最大堆:先把最小堆调整成最大堆,B和D换位置;

(2)堆排序循环:在一个正常排好序的数组中,最大元素应该放在最后一个位置,要把根结点与最后一个结点换位置,D下来,B上去;

(3)剩余/当前元素调整最大堆(调整时以0为根结点):堆的规模-1,D就排除在外,D已经在其正确位置上了,无论后面再做什么这个D都不用动了。

(2)和(3)其实可以合并起来说。

image.png

重复上述步骤。

image.png

现在堆中只剩下A和B两个元素。

B和A做最后一次交换。

此时完成排序。

堆的元素是从第一个下标为1的元素开始计数的,A[0]是不放任何真元素的,而是放的哨兵,而在排序的算法中,用户并不知道应该在前面留个哨兵元素,用户是从第0个元素就开始存在的,所以堆中的元素也是从0开始记的,这就导致了任何一个结点跟它的孩子结点的下标关系就不同了。

那么在堆排序中,元素下标从0开始,则对于下标为i的元素,左孩子的下标为2i+1,右孩子的下标为2i+2。

(对比5.1和9.3)

随机排列:平均比较次数-平均时间复杂度

image.png

源码:堆排序-优化(低于希尔排序-Sedgewick增量序列)


/*
排序-堆排序-真正的堆排序-伪代码
把BuildHeap函数编写的更具体一些.
*/
void Heap_Sort2(ElementType A[], int N){

	/*
	反复调用就把一个最大堆建立起来了.
	*/
	for(i = N/2-1; i >= 0; i--){
		/*
		核心调用向下过滤函数
		i根结点所在位置.
		N当前这个堆里一共有多少个元素.
		*/
		ParcDown(A, i, N);
	}
	/*
	堆排序的循环
	当上述最大堆建立起来后,A[0]根结点存的一定是最大的元素.
	i存的是当前的最后一个元素它的下标.
	*/
	for(i = N-1; i > 0; i--){
		/*
		把根结点换到当前这个堆的最后一个元素的位置上去.
		*/
		Swap(&A[0], &A[i]);
		/*
		继续把剩余的元素调整成一个最大堆.
		调整时以0为根结点,
		i是当前这个最大堆的元素的个数.
		*/
		PercDown(A, 0, i);
	}

}
/*
元素交换
*/
void Swap(ElementType *a, ElementType *b){
	ElementType = *a;
	*a = *b;
	*b = t;
}
/*
调整最大堆-根据5.1改造
将N个元素的数组中以A[p]为根的子堆调整为最大堆.
*/
void PercDown(ElementType A[], int N){

	int Parent, Child;
	ElementType X;

	/* 取出根结点存放的值 */
	X = A[p];
	for(Parent = p; (Parent * 2 + 1) < N; Parent = Child){
		Child = (Parent * 2 + 1);
		if((Child != N - 1) && (A[Child] < A[Child + 1])){
			/* Child指向左右子结点的较大者 */
			Child++;
		}
		if(X >= A[Child]){
			/* 找到了合适位置 */
			break;
		}else{
			/* 下滤X */
			A[Parent] = A[Child];
		}
	}
	A[Parent] = X;

}

/* 
堆排序 
*/
void HeapSort(ElementType A[], int N){
	int i;
	for(i = N/2-1; i >= 0; i--){
		/* 建立最大堆 */
		PercDown(A, i, N);
	}
	for(i = N - 1; i > 0; i--){
		/* 删除最大堆顶 */
		Swap(&A[0], &A[i]);
		/* 根据当前元素建立最大堆 */
		PercDown(A, 0, i);
	}
}


标题:(数据结构)第九讲-排序(9.3 堆排序)
作者:yazong
地址:https://blog.llyweb.com/articles/2023/10/22/1697978089694.html