数据结构-选择排序-堆排序

选择排序

①简单选择排序。

②树形选择排序。

③堆排序。←this



堆排序(Heap Sort)只需要一个记录大小的辅助空间,每个待排序的记录仅占有一个存储空间。
堆定义:满足ki<=k2;且ki<=k(2i+1) (i=1,2,...,n/2)完全二叉树且父比子大(或父比子小)
堆排序方法对记录较少的文件不值得提倡,对n较大的文件还是很有效的。因为其运行时间主要耗费在建初始堆,和调整新堆
最大优点是,堆排序的最坏情况下,时间复杂度也是O(nlogn)。
首先介绍堆运算:
Sift-Up
简单来说就是二叉树的结点(样例用叶子)突然变大,需要重新对堆进行排列。从下往上传递(交换)更大的值
            20                                                         20                                                      25
        /          \                                                 /         \                                                /         \
    17              9             5变成25                17           9               25向上              20            9
    /   \           /    \           ---------->              /   \          /    \           ---------->           /   \          /    \  
 10    11      4      5                                10      11    4     5                              10      17    4     5
 /  \    /                                                 /  \        /                                             /  \        /
3  7  5                                               3    7    25                                          3    7    11
输入:数组H[ 1...n ] 和位于1和n之间的索引 i
输出:上移H[ i ](如果需要),使他不大于父节点。
//            堆a,根i 
void sift_up(int *a,int i){         
	bool done=false;
	if(i==1) return ;               //总根 
	while(i!=1 && !done){
		if( a[i] > a[i/2] )         //子比父大,换 
			swap(a[i],a[i/2]);
		else done = true;  
		i=i/2;
	}
}



Sift-Down
简单来说就是二叉树的某根突然变小,需要重新对堆进行排列,从上往下传递(交换)更小的值
            20                                                         20                                                    20
        /          \                                                 /         \                                              /         \
    17              9             17变成3                 3            9                3往下               11           9
    /   \           /    \           ---------->              /   \          /    \           ---------->           /   \          /    \  
 10    11      4      5                                10      11    4     5                              10       5     4       5
 /  \    /                                                 /  \        /                                             /  \       /
3  7  5                                               3    7    5                                            3    7    3
输入:数组H[ 1...n ] 和位于1和n之间的索引i
输出:下移H[ i ](如果需要),以使它不小于子节点。
//             堆a,子根i ,总n 
void sift_down(int *a,int i,int n){ 
	bool done=false;
	if(2*i > n) return ;            //根i是叶子 
	while(2*i<=n && !done){
		i=2*i;                      //根i叶子 
		if(i+1<=n && a[i+1]>a[i])   //根i两个叶子里选个大的 
			i=i+1;
		if(a[i/2] < a[i])           // 父比子大,换 
			swap(a[i],a[i/2]);
		else done = true;
	}
}



插入数值
在树的最后添上一个数,然后执行Sift-Up,从下到上重新排列一下,保持堆的特性
            20                                                         20                                                       20
        /          \                                                 /         \                                               /          \
    17              9               插入18                 17             9               Sift-Up               18              9
    /   \           /    \           ---------->              /   \          /    \           ---------->           /     \           /    \  
 10    11      4      5                                10      11    4     5                              10        17       4       5
 /  \    /                                                 /  \        /  \                                           /  \      /    \
3  7   5                                                3    7    5     18                                    3    7   5      11
输入:数组H[ 1...n ] 和元素x。
输出:新的堆H[ 1...n+1 ],x为其元素之一。
//             堆a,数x,总n 
void insert(int *a,int x,int &n){
	n=n+1;
	a[n]=x;
	sift_up(a,n);
}


删除数值
从堆里删除元素,先将最后一个叶子跟要删除的数值交换,
如果最后一个叶子比较那么Sift-Up,从下到上重新排列一下
如果最后一个叶子比较那么Sift-Down,从上到下重新排列一下
            20                                                         20                                                    20                                                20
        /          \                 要删除10                  /          \                                              /         \                                        /         \
     17              9            10和5交换              17            9               删除10              17           9            Sift-Down         17             9
    /   \           /    \           ---------->              /   \          /    \           ---------->           /   \          /    \          ---------->       /   \          /    \    
 10    11      4      5                                       11    4     5                                  5       11     4       5                         7         11        4       5
 /  \    /                                                 /  \        /                                             /  \                                                /    \       
3  7    5                                                3    7     10                                          3    7                                            3      5
输入:非空堆H[ 1...n ] 和位于1和n之间的索引i
输出:删除H[ i ]之后的新堆H[ 1...n-1]
//           堆a,下标i,总n 
void delet(int *a,int i,int &n){
	int x=a[i],y=a[n];a[n]=0;
	n=n-1;
	if(i == n+1)                 //删的是最后一个点,那已经删掉了
		return ; 
	a[i]=y;                      //用最后一个叶子覆盖这个结点 
	if(y>=x) sift_up(a,i);       //换上的比原来大,上浮 
	else sift_down(a,i,n);       //小,下沉 
}


创建堆

从叶子的上一个根到整个的根节点,每一次都将小的数沉下来
★sift-down是根向叶子的调整,从叶子开始毫无意义,所以直接跳过叶子部分
//         数组a,长度n 
void build(int *a,int n){        
	for(int i=n/2;i>=1;i--){     //从一半开始到根节点 
		sift_down(a,i,n);
	}
}


⑥堆排序
因为每次堆顶端都是最大值,那么,把最后的那个换成堆顶端,那么最大值就在最后面了。
依次把当前最大值放到后面,那么就可以排出从小到大的顺序了。
所要用到的步骤是:⑤创建堆+②Sift-Down
            20                                                        5                                                    17
        /          \                                                 /         \                  无视20              /         \
   17             9             20交换5                17            9         从顶sift-down      11           9          17交换7 
    /   \           /    \           ---------->              /   \          /    \           ---------->           /   \          /    \      ---------->  ...省略...
 10    11      4      5                                10      11    4     5                              10      5     4       5
 /  \    /                                                 /  \        /                                             /  \       /
3  7    5                                               3    7    20                                           3    7   20

#include <cstdio>  
#include <cstring>  
#include <iostream>  
using namespace std;  
const int N=30;
void print(int *tree){
	int altm=0;
	for(int j=1;j<5;j++){
		int tm=1;
		for(int i=1;i<j;i++)
			tm*=2;
		for(int i=1;i<=tm;++i)
		{
			printf("%d ",tree[altm+i]);
		}
		printf("\n");altm+=tm;
	}
} 
//             堆a,子根i ,总n 
void sift_down(int *a,int i,int n){ 
	bool done=false;
	if(2*i > n) return ;            //根i是叶子 
	while(2*i<=n && !done){
		i=2*i;                      //根i叶子 
		if(i+1<=n && a[i+1]>a[i])   //根i两个叶子里选个大的 
			i=i+1;
		if(a[i/2] < a[i])           // 父比子大,换 
			swap(a[i],a[i/2]);
		else done = true;
	}
}
//         数组a,长度n 
void build(int *a,int n){        
	for(int i=n/2;i>=1;i--){     //从一半开始到根节点 
		sift_down(a,i,n);
	}
}
void heapSort(int *a,int n){
	build(a,n);
	for(int j=n;j>=2;j--){
		swap(a[1],a[j]);
		sift_down(a,1,j-1);
	}
}
int main()
{
	int a[N]={0,3,7,5,10,5,4,9,20,17,11};
	int len=10;
	print(a); 
	//build(a,len);print(a);printf("---%d\n",len);
	//insert(a,13,len);print(a);printf("---%d\n",len);
	//delet(a,5,len);print(a); printf("---%d\n",len);
	heapSort(a,len);
	print(a); 
	for(int i=1;i<=len;i++){
		printf("%d ",a[i]);
	}printf("\n");
	return 0;
}

那么问题来了?从大到小排序怎么弄呢?
把sift_down()里的第七行和第九行改一下方向就行了,每次堆顶都是最小值->最小堆

堆排序到此结束。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值