堆排序
堆是一个数组,它可以被看成一个近似的完全二叉树,树上的每一个结点对应数组中的一个元素。除了最底层外,改树是完全充满的,而且是从左往右填充的。
堆可以分成两种形式:最大堆和最小堆。
在堆排序算法中,我们使用的是最大堆。最小堆一般用于构建优先队列。
我们知道,在最大堆中,最大堆性质是指除了根以外的所有结点i都要满足:
A[parent(i)]>=A[i]
也就是说,某个结点的值至多与其父节点一样大,因此,堆中最大的元素存放在根结点中。
所有我们需要一个用于维护最大堆性质的函数。它的输入是一个数组A和一个下标i,我们维护最大堆性质函数的作用就是让以下标i为根结点的堆遵循最大堆性质。
void max_heap(int *a,int i,int n) //n=a.length
{
int j=i*2+1
int temp=a[i]
while(j<n)
{
if(a[j]<a[j+1]&&j+1<n)
j++
if(a[i]>=a[j])
break
a[i]=a[j]
i=j
j=i*2+1
}
a[i]=temp
}
现在我们就可以将我们要排序的数据建成一个最大堆数组。
void build_max_heap(int *a,int n)
{
for(int i=n/2-1;i>=0;i--)
max_heap(a,i,n);
}
现在我们拥有了建立最大堆的能力,那么我们就可以用它来排序,因为最大的元素就在根结点处,所以我们将根结点放入数组的最后,然后将剩下的元素再建成一个最大堆,再将根结点放入数组的最后,依次循环,我们就排好序了。
void heap_sort(int *a,int n)
{
for(int i=n-1;i>=0;i--)
{
build_max_heap(a,i+1);
swap(a[0],a[i]);
}
}
我们可以看出堆排序的时间复杂度跟归并排序相同是O(nlgn),但是它的空间复杂度与插入排序相同都是常数。
快速排序
快速排序也使用了分治思想,它的原理是找一个主元,然后将数组里的每一个元素与主元相比较,比主元小的放在主元左边,比主元大的放在主元右边,然后以主元为界,对左右两个子数组进行相同的操作,依次下去,就可以排好序了。
下面我们以数组最后一个元素为主元:
int partition(int *a,int i,int n)
{
int key=a[n];
int m=i-1;
for(int j=i;j<n;j++)
{
if(a[j]<=key)
{
m+=1;
swap(a[m],a[j]);
}
}
swap(a[m+1],a[n]);
return m+1;
}
void quick_sort(int *a,int i,int n)
{
int q;
if(i<n)
{
q=partition(a,i,n);
quick_sort(a,i,q-1);
quick_sort(a,q+1,n);
}
}
我们可以看到快排的最坏情况时间复杂度是O(n^2),而在元素互异的情况下,期望时间复杂度是O(nlgn)。