排序算法(1):简单选择排序和堆排序

1.简单选择排序

(1)本质:每一趟给定待排序序列A[ 1......n ] ,选择出第i小元素,并和A[i]交换。

代码:

/*************************************************
算法:简单选择排序(升序)
时间复杂度为O(n^2)
**************************************************/
void simpleSelectSort(int *arr , int len)
{
	for(int i =0;i<len-1;i++)
	{
		int minIndex=i;  //辅助交换内存空间
		for (int j=i+1;j<len;j++)
		{
			if (arr[j]<arr[minIndex])
			{
				minIndex=j;
			}
		}
		if(minIndex != i)
			swap(&arr[i],&arr[minIndex]);
		cout<<"第"<<i+1<<"趟:";
		printArr(arr,len);
		cout<<endl;
	}
}
运行结果:



(2)性能分析

     简单选择排序记录移动操作次数较少,这点优于冒泡排序。有两层循环,所以时间复杂度为O(n^2)

2.堆排序

2.1 堆的定义

      n个元素的序列{k1,k2,…,kn}当且仅当满足下列关系之一时,称之为堆。

  情形1:ki <= k2i 且ki <= k2i+1 最小化堆小顶堆

  情形2:ki >= k2i 且ki >= k2i+1 最大化堆大顶堆

  其中i=1,2,…,n/2向下取整;


2.2 堆排序(以大顶堆为例,根节点从0开始)

     输出堆顶最大值之后,使得剩余n-1个元素的序列重建一个堆;如此反复执行,便能得到一个有序序列,这个过程称之为堆排序。(升序---大顶堆,降序---小顶堆)


(1)堆的存储

 一般用数组来表示堆,若根结点存在序号0处, i结点的父结点下标就为(i-1)/2。i结点的左右子结点下标分别为2*i+12*i+2

  (注:如果根结点是从1开始,则左右孩子结点分别是2i和2i+1。)

最大化堆如下:

实现堆排序需要解决两个问题:

    1.如何由一个无序序列建成一个堆?

    2.如何在输出堆顶元素之后,调整剩余元素成为一个新的堆?


问题2: 一般在输出堆顶元素之后,视为将这个元素排除,然后用表中最后一个元素填补它的位置,自上向下进行调整:首先将堆顶元素和它的左右子树的根结点进行比较,把最大的元素交换到堆顶;然后顺着被破坏的路径一路调整下去,直至叶子结点,就得到新的堆。自堆顶至叶子的调整过程为"筛选"过程。无序序列建立堆的过程就是一个反复"筛选"过程。

帅选过程代码:

/****************************************************
堆排序是一种选择排序;在最坏的情况下,其时间复杂度也为O(nlogn)
其运行时间主要耗费在建初始堆和调整建新堆时进行的反复“筛选”上
****************************************************/
/*
函数功能:当堆的某一个节点破坏了堆的性质,调用此函数进行调整剩余的元素,使其为新堆
k      :k节点破坏了堆的性质
*/
void maxHeap(int *arr,int len,int k)
{
	//递归边界条件
	if (k>len/2-1)   //最后一个非叶子节点的下标为len/2-1;
	{
		return;
	}
	//1.比较k节点左右孩子的值,找出左右孩子的较大值
	int i=0;  
	//因为假设根结点的序号为0而不是1,所以i结点左孩子和右孩子分别为2i+1和2i+2
	if ((2*k+2<=len-1)&&arr[2*k+1]<arr[2*k+2])
		i=2*k+2;
	else if(2*k+1<=len-1)
		i=2*k+1;
	if (arr[k]<arr[i])
	{
		swap(&arr[k],&arr[i]);
		maxHeap(arr,len,i);
	}
}

问题1:构建初始堆

     构建堆就是对所有的非叶子节点进行筛选。最后一个非叶子节点的下标为([n/2]-1),所以筛选只需从([n/2]-1)节点开始,自下向上的进行调整,直到堆顶。

/*****************************************************************************
1.给定一个数组,首先根据该数组元素构造一个完全二叉树。
2.从最后一个非叶子结点开始,每次都是从父结点、左孩子、右孩子中进行比较交换;
3.交换可能会引起孩子结点不满足堆的性质,所以每次交换之后需要重新对被交换的孩子结点进行调整。
最后一个非叶子节点下标:[n/2]-1 (根节点的下标为0), n表示节点个数
******************************************************************************/
void createHeap(int *arr , int len)
{
	//最大堆----------自下向上调整,数组的下标从0开始
	for (int i=len/2-1;i>=0;i--)
	{
		maxHeap(arr,len,i);
	}
}

(3)堆排序

     首先输出堆顶元素(因为它是最值),将堆顶元素和最后一个元素交换,这样,第n个位置(即最后一个位置)作为有序区,前n-1个位置仍是无序区,对无序区进行调整,得到堆之后,再交换堆顶和最后一个元素,这样有序区长度变为2。不断进行此操作,将剩下的元素重新调整为堆,然后输出堆顶元素到有序区。每次交换都导致无序区-1,有序区+1。不断重复此过程直到有序区长度增长为n-1,排序完成。

void heapSort(int *arr , int len)
{
	//1. 构建堆
	createHeap(arr,len);
	//2.堆排序
	for (int i=0;i<len-1;i++)
	{
		//堆顶元素与最后一个元素交换
		swap(&arr[0],&arr[len-1-i]);
		//调整堆,堆顶破坏了堆的性质
		maxHeap(arr,len-i-1,0);
	}
}


(4)反思堆排序

     仔细回想一下筛选法调整堆的过程我们发现,第i次调整堆,其实就是把A中的第i大元素放到首位置A[0],然后A[0]和A[n-i-1]互换.这样A[(n-i-1)...n]就已经有序,于是又把A[0...n-i-2]看成一个堆再进行排序,如此重复。调整堆不就是选择出待排序序列中的最大值吗?所以堆排序的本质和选择排序的本质是一样的。选择一个待排序序列中的最小(大)值,这就是选择排序的本质。所以,堆排序是一种选择排序。

(5)性能分析

    堆排序时间=建堆时间+调整堆时间。从上文中知道建堆时间复杂度为O(n*log2n)。筛选法调整堆(maxHeap函数)时间O(log2n),总共循环了n-1次maxHeap函数,所以调整堆时间复杂度为O(n*log2n)。得出堆排序时间复杂度O(n*log2n)。且在最坏情况下时间复杂度也是O(n*log2n)。此外堆排序是不稳定的原地排序算法。


(6)测试代码
void test()
{
	int arr[]={16,20,8,7,17,3,30,1,51,9,6};
	int len = sizeof(arr)/sizeof(int);
	cout<<"排序前:";
	printArr(arr,len);
	cout<<endl;

	//简单选择排序
	simpleSelectSort(arr,len);
	cout<<"简单选择排序:";
	printArr(arr,len);
	cout<<endl;

	//堆排序
	heapSort(arr , len);
	cout<<"堆排序:";
	printArr(arr,len);
	cout<<endl;
}

附件:辅助函数
void swap(int *p,int *q)
{
	int tmp;
	tmp =*p;
	*p = *q;
	*q = tmp;
}
void printArr(int *arr,int len)
{
	for (int i=0;i<len;i++)
	{
		cout<<arr[i]<<" ";
	}
	cout<<endl;
}



  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值