选择排序有直接选择和堆排序俩种,分别来看:
一、直接选择排序
选择排序即在第N次遍历中,选出最小(最大)的数据,和第N个位置交换。
举个例子:待排序数据为 5,7,2,9,0
第一次遍历,找出剩余最小数据0,交换到第一位,结果为0,7,2,9,5
第二次遍历,找出剩余最小数据2,交换到第二位,结果为0,2,7,9,5
第三次遍历,找出剩余最小数据5,交换到第三位,结果为0,2,5,9,7
第四次遍历,找出剩余最小数据7,交换到第四位,结果为0,2,5,7,9
第五次遍历,找出剩余最小数据9,交换到第五位,结果为0,2,5,7,9
可以看出,对于N个数据,共需要N*N次遍历,时间复杂度为O(N2),空间复杂度为O(1);
对于稳定性,我们可以通过一个例子验证:
排序前:1,3,3*,2
排序后:1,2,3*,3
可以看出,直接选择排序是不稳定的
代码实现:
void selectsort(int arr[],int len)
{
int tmp=0;
int min=0;
for(int i=0;i<len;i++)
{
min=i;
for(int j=i+1;j<len;j++)
{
if(arr[j]<arr[min])
{
min=j;
}
}
tmp=arr[min];
arr[min]=arr[i];
arr[i]=tmp;
}
}
二、堆排序
首先,我们需要明白大根堆和小根堆:
将数据想象为一棵树
大根堆:父节点的数据大于子节点,任何一个父节点都存在。
小根堆:父节点的数据小于子节点。
如上图所示,3, 8, 15, 31, 25是一个典型的小根堆。
堆中有两个父结点,元素3和元素8。
元素3在数组中以R[0]表示,它的左孩子结点是R[1],右孩子结点是R[2]。
元素8在数组中以R[1]表示,它的左孩子结点是R[3],右孩子结点是R[4],它的父结点是R[0]。可以看出,它们满足以下规律:
设当前元素在数组中以R[i]表示,那么,
它的左孩子结点是:R[2*i+1];
它的右孩子结点是:R[2*i+2];
它的父结点是:R[(i-1)/2];
R[i] <= R[2*i+1] 且 R[i] <= R[2i+2]
堆排序的算法分为俩步:
(1)根据初始数组去构造初始堆,以大根堆为例,构建一个完全二叉树,保证所有的父节点都比它的孩子节点大。
(2)每次交换第一个和最后一个元素,将最大值放在最后,然后把剩下元素重新调整为大根堆。
时间复杂度:O(n+nlog2n),即为O(nlog2n)
当想得到一个序列中第k个最小的元素之前的部分排序序列,最好采用堆排序。
因为堆排序的时间复杂度是O(n+klog2n),若k≤n/log2n,则可得到的时间复杂度为O(n),多用于大数据处理。
空间复杂度:O(1)
稳定性:不稳定
堆在调整过程中,关键字进行比较和交换所走的是该结点到叶子结点的一条路径,
因此对于相同的关键字就可能出现排在后面的关键字被交换到前面来的情况
代码实现:
//大根堆排升序
void heapadjust(int arr[],int i,int len)
{
for(int j=2*i;j<len;j=2*j)
{
if(j<len&&arr[j]<arr[j+1])
{
j++;
}
if(arr[j]<arr[i])break;
arr[0]=arr[i];
arr[i]=arr[j];
arr[j]=arr[0];
i=j;
}
}
void heapsort(int arr[],int len)
{
for(int i=len/2;i>0;i--)
{
heapadjust (arr,i,len);
}
for(int j=len;j>0;j--)
{
arr[0]=arr[1];
arr[1]=arr[j];
arr[j]=arr[0];
heapadjust (arr,1,j-1);
}
}
//小根堆排降序
void heapdownjust(int arr[],int i,int len)
{
for(int j=2*i;j<len;j=2*j)
{
if(j<len&&arr[j]>arr[j+1])
{
j++;
}
if(arr[j]>arr[i])break;
arr[0]=arr[i];
arr[i]=arr[j];
arr[j]=arr[0];
i=j;
}
}
void heapdownsort(int arr[],int len)
{
for(int i=len/2;i>0;i--)
{
heapdownjust (arr,i,len);
}
for(int j=len;j>0;j--)
{
arr[0]=arr[1];
arr[1]=arr[j];
arr[j]=arr[0];
heapdownjust (arr,1,j-1);
}
}