交换排序
所谓交换,就是根据序列中两个元素关键字的比较结果来对这两个记录在序列中的位置。
快速排序
快速排序是对冒泡排序的一种改进。其基本思想是基于分治法的:在待排序表 L[1⋯n] 中任取一个元素piovt作为基准,通过一趟排序将待排序的表划分为独立的两部分 L[1⋯k−1] 和 L[k+1⋯n] ,使得 L[1⋯k−1] 中的所有元素小于pivot, L[k+1⋯n] 中的元素都大于或者等于pivot,则piovt放在了其最终的位置 L(k) 上,这个过程称为一次快速排序。而后分别递归地对两个子表重复上述过程,直至每部分内只有一个元素或空为止,即所有的元素都放在了其最终的位置上。代码如下:
int Partition(int A[],int low ,int high)
{
int pivot = A[low];
while(low < high){
while(low<high&&A[high] >= pivot) --high;
A[low] = A[high];
while(low<high&&A[low]<=pivot) ++low;
A[high] = A[low];
}
A[low] = pivot;
return low;
}
void QuickSort(int A[],int low,int high)
{
if(low < high){
int pivot = Partition(A,low,high);
QuickSort(A,low,pivot-1);
QuickSort(A,pivot+1,high);
}
}
空间效率:快排是递归的,需要借助一个递归工作栈来保存每一层递归调用的必要信息。最坏情况下空间复杂度为
O(n)
,平均情况下空间复杂度为
O(logn)
。
时间效率:最坏情况下,当待排序的表基本有序或者是逆序时时间发杂度为
O(n2)
,一般情况下的时间复杂度为
O(nlogn)
稳定性:在划分算法中,若右端区间存在两个关键字相同且均小于基准值的记录,则在交换到左端区间后,它们的相对位置会发生变化。所以快排不是稳定的排序方法。
堆排序
堆排序时一种树形选择排序的放法。它的特点是:在排序的过程中,将
L[1⋯n]
看成是一颗完全二叉树的顺序存储结构,利用完全二叉树中双亲节点和孩子节点之间的关系,在当前无序区中选择最大或最小的元素。
堆的定义如下: n个关键字序列
L[1⋯n]
称为堆,当且仅当该序列满足:
- L(i)≤L(2i) 且 L(i)≤L(2i+1) (1≤i≤⌊n/2⌋) (小根堆)
- L(i)≥L(2i) 且 L(i)≥L(2i+1) (1≤i≤⌊n/2⌋) (大根堆)
堆排序的关键就是构造初始堆,对于初始序列建堆,就是一个反复筛选的过程。那个节点的完全二叉树,最后一个节点是第 ⌊n/2⌋ 个节点为根的子树筛选,使该子树成为堆。之后向前一次对各节点 (⌊n/2⌋∼1) 为根的子树进行筛选,看该节点是否大于其左右子节点的值,若不是将左右子节点中较大值与之交换,交换后可能会破坏下一级的堆,于是继续采用上述放法构造下一级的堆,直到以该节点为根的子树构成堆为止。反复利用上述调整堆的放法建堆,直到根节点。代码如下:
void AdjustDown(int A[],int k,int len)
{
A[0] = A[k];
for(int i=2*k;i<=len;i*=2){ //找到k的左孩子节点
if(i<len&&A[i]<A[i+1]) //找左右孩子大的节点
i++;
if(A[0]>=A[i]) break;
else{
A[k] = A[i]; //将A[i]调整到双亲节点上
k = i; //修改k值,以便向下继续筛选
}
}
A[k] = A[0]; //被筛选的值放入最终的位置
}
void BuildMaxHeap(int A[],int len)
{
for(int i=len/2;i>0;i--)
AdjustDown(A,i,len);
}
void HeapSort(int A[],int len)
{
BuildMaxHeap(A,len);
for(int i=len;i>1;i--){
swap(A[i],A[1]);
AdjustDown(A,1,i-1);
}
}
大根堆,堆顶元素就是最大值,每次将堆底元素和堆顶元素交换之后堆剩下的序列继续调整,如此重复直到堆中只剩一个元素为止。
空间效率:
O(1)
时间效率:建堆的时间是
O(n)
,之后有
n−1
次向下调整的操作,每次调整的时间复杂度为
O(h)
,所以堆排序的时间复杂度为
O(nlogn)
稳定性:在进行筛选时,有可能把后面相同关键字的元素调整到前面,所以堆排序是一种不稳定的排序方法。