本文主要参考《算法导论》和维基百科
1.合并排序
算法中经常采用分治法:将原问题划分成n个规模较小而结构与原问题相似的子问题,递归解决这些子问题,然后在合并其结果,就得到原问题的解。在每一层递归上都有三个步骤:
a.分解(Divide):将原问题分解成一系列子问题;
b.解决(Conquer):递归地解各个子问题,若子问题足够小,则直接求解;
c.合并(Combine):将子问题的结果合并成原问题的解。
合并排序算法就是典型的分治法,其步骤可以描述为:
分解:将n个元素分成各含n/2个元素的子序列;
解决:用合并排序算法对两个子序列递归排序;
合并:合并两个已排序的子序列以得到排序结果。
对子序列排序时,其长度为1时递归结束。单个元素被视为已是排好序的(此为递归结束的出口)。
c语言实现:
/************合并排序start************/
/*
*合并两个已排好序的子序列 a1和a2
*/
static void merge(int *a1,int length1,int *a2,int length2){
int ret[length1+length2];
int i=0;
int j=0;
int k=0;
while(i<length1&&j<length2){//两个数组的游标都没有到末尾时执行循环
ret[k++]= a1[i]<=a2[j]?a1[i++]:a2[j++];
}
//将a1或者a2中剩余的元素全部放入到ret中,循环结束后a1和a2中只会有其中一个还有剩余元素
for(;i<length1;i++){
ret[k++]=a1[i];
}
for(;j<length2;j++){
ret[k++]=a2[j];
}
for (int ii = 0; ii < (length1+length2); ++ii){
a1[ii] = ret[ii];
}
}
void mergeSort(int* a,int length){
if(a!=NULL&&length>1){
int* left = a;
int left_length= length/2;
int* right = a+length/2;
int right_length = length - length/2;
mergeSort(left,left_length);
mergeSort(right,right_length);
merge(left,left_length,right,right_length);
}
}
/************合并排序 end************/
java语言实现:
/************合并排序start************/
public static int[] mergeSort(int[] a){
if(a.length==1){//单个元素被视为已是排好序的。
return a;
}
int[] Left = new int[a.length/2];
int[] Right = new int[a.length-a.length/2];
System.arraycopy(a, 0, Left, 0, a.length/2);
System.arraycopy(a, a.length/2, Right, 0, a.length-a.length/2);
Left = mergeSort(Left);
Right = mergeSort(Right);
return merge(Left,Right);
}
/*
* 合并两个已排好序的子序列 a1和a2
*/
private static int[] merge(int[] a1,int[]a2){
int[] ret = new int[a1.length+a2.length];
int i=0;
int j=0;
int k=0;
for(k=0;k<ret.length;k++){
ret[k]= a1[i]<=a2[j]?a1[i++]:a2[j++];
if(i<a1.length&&j<a2.length){//两个数组的游标都没有到末尾时继续执行循环
continue;
}else{
break;
}
}
//将a1或者a2中剩余的元素全部放入到ret中,循环结束后a1和a2中只会有其中一个还有剩余元素
for(;i<a1.length;i++){
ret[++k]=a1[i];
}
for(;j<a2.length;j++){
ret[++k]=a2[j];
}
return ret;
}
/************合并排序 end************/
2堆排序
堆数据结构式一种数组对象。它可以被视为一颗完全二叉树。给定某个节点的下标i,其父节点,左儿子,右儿子的下标可以如下计算:
parent(i) =floor(i/2);left(i)=2*i; right(i)=2*i+1;
如果数组下标从零开始,则为:parent(i) = floor((i-1)/2);left(i)=2*i+1; right(i)=2*i+2;
当用数组表示n个元素的堆时,叶子节点下标是n/2+1,n/2+2.。。n(若从零计数,则为n/2,n/2+1...n-1)
二叉堆有两种:最大堆和最小堆。最大堆的特性是指除了根以外的每个节点的值最多是和其父节点的值一样大。这样,堆中最大元素的就存放在根节点中,并且在以某一节点为根的子树中,个节点的值都不大于该子树根节点的值。最小堆的特性则刚好相反。
在堆排序中使用的事最大堆,最小堆通常在构造优先队列时使用。
堆排序算法步骤为:建立最大堆,交换堆顶点(根)和最后一个元素,然后把最后一个元素从堆中排除,形成一个新堆,元素减少一个, 并对其进行保持最大堆操作。
其中保持堆性质(Max-Heapify)是堆排序中最重要的子程序。其输入为一个数组A和下标i。当Max-Heapify调用时,假定以left(i)和right(i)为根的两颗二叉树都是最大堆,但这时A[i]可能小于其子女,这样就违反了最大堆性质,Max-Heapify让A[i]在最大堆中下降,使得以i为根的子树成为最大堆。
c语言实现:
/************堆排序 start************/
/*保持堆的性质
* 根据传入的位置,向下保持为最大堆属性
* 数组从零开始计数,父节点(i-1)/2,左节点(2*i)+1,右节点(2*i+1)+1
*size<=heap的长度,通过控制传入size值,可以修改堆的大小,原数组大小本身不变化
*/
static void maxHeapify(int heap[],int size, int i){
int largest = i;//默认为父节点值最大,和左右子节点进行比较
int left = (2*i)+1;
int right = (2*i+1)+1;
if(left<size){
largest = heap[left]>heap[i]?left:i;
}
if(right<size){
largest = heap[right]>heap[largest]?right:largest;
}
if(largest!=i){//如果发生交换继续向下保持为最大堆
swap_array_int(heap,i,largest);
maxHeapify(heap,size,largest);//保持最大堆时,堆大小不用变
}else{
return;
}
}
/*将数组建为最大堆
* 没有子节点的不需要创建最大堆,从最后一个的父节点开始
* 当用数组表示n个元素的堆时,叶子节点下标是n/2+1,n/2+2.。。n(若从零计数,则为n/2,n/2+1...n-1)
*/
static void buildMaxHeap(int a[], int length){
for(int i=length/2;i>=0;i--){
maxHeapify(a,length,i);
}
}
void heapSort(int a[],int length){
if(a==NULL)return;
//maxHeapify(a,length,1);
buildMaxHeap(a,length);//建立最大堆
for(int i=length-1;i>=1;--i){
swap_array_int(a,i,0);//因为是最大堆,交换堆顶点和最后一个元素
maxHeapify(a,i,0);//把最后一个位置从堆中去除(通过递减i实现),形成一个新堆,元素减少一个 ,然后对其进行保持最大堆
}
}
/************堆排序 end************/
java语言实现:
/************堆排序 start************/
public static void heapSort(int []a){
buildMaxHeap(a);//建立最大堆
for(int i=a.length-1;i>=1;--i){
Utils.swap(a,i,0);//因为是最大堆,交换堆顶点和最后一个元素
maxHeapify(a,i, 0);//把最后一个位置从堆中去除(通过递减i实现),形成一个新堆,元素减少一个 ,然后对其进行保持最大堆
}
}
/*保持堆的性质
* 根据传入的位置,向下保持为最大堆属性
* 数组从零开始计数,父节点(i-1)/2,左节点(2*i)+1,右节点(2*i+1)+1
* size<=heap的长度,通过控制传入size值,可以修改堆的大小,原数组大小本身不变化
*/
private static void maxHeapify(int []heap,int size, int i){
int largest=i;//默认为父节点值最大,和左右子节点进行比较
int left = (2*i)+1;
if(left<size){
largest = heap[left]>heap[i]?left:i;
}
int right = (2*i+1)+1;
if(right<size){
largest = heap[right]>heap[largest]?right:largest;
}
if(largest!=i){//如果发生交换继续向下保持为最大堆
Utils.swap(heap,i,largest);
maxHeapify(heap,size,largest);//保持最大堆时,堆大小不用变
}else{
return;
}
}
/*将数组建为最大堆
* 没有子节点的不需要创建最大堆,从最后一个的父节点开始
* 当用数组表示n个元素的堆时,叶子节点下标是n/2+1,n/2+2.。。n(若从零计数,则为n/2,n/2+1...n-1)
*/
private static void buildMaxHeap(int []a){
for(int i=a.length/2;i>=0;i--){
maxHeapify(a,a.length,i);
}
}
/************堆排序 end************/
3.快速排序
快速排序也是基于分治模式:
分解:数组A[p..r]被划分成两个子数组(可能为空)A[p..q-1]和A[q+1..r],使得A[p..q-1]中的每个元素都小于A[q],而且小于等于A[q+1,r]中的元素,下标q在划分过程中计算(通常通过伪随机方式选取);
解决:通过递归调用快速排序,对子数组A[p..q-1]和A[q+1..r]排序
合并:因为两个子数组是就地排序,不需要合并操作就已排序。
伪代码描述快速排序过程:
QUICK-SORT(A,p,r)
if p<r
<then q=PARTITION(A,p,r)
QUICK-SORT(A,p,q-1)
QUICK-SORT(A,q+1,r)
快速排序算法的关键是PARTITION过程,它对子数组A[p..r]进行就地重排
PARTITION(A,p,r)
x=A[r]
i=q-1
for j=p to j=r-1
do if A[j]<=x
then i=i+1
exchange A[i] ,A[j]
exchange A[i+1],A[r]
return i+1
c语言实现
/************快速排序 start************/
/*
*partition是快速排序的关键过程 ,对子数组a[p,r]就地重排
*选择一个数组元素作为主元,围绕它来划分数组a[p,r],数组被划分为四个区域(可能有空的)
*游标i用于增长比主元的小的元素位置空间,
*游标j用于从左向右依次将元素和主元进行比较
*/
static int partition(int a[],int p,int r){
swap_array_int(a,get_randomInt(p,r),r);//随机选取主元并放置到子数组末尾
int x = a[r];
int i = p-1;//用于移动比主元小的元素游标
for(int j=p;j<r;j++){
if(a[j]<=x){//发现比主元小的元素
swap_array_int(a, ++i, j);//比主元小的元素依次排列(先不分大小)
}
}
swap_array_int(a, ++i, r);//将主元放置在比它小的右边
return i;
}
/*
*数组a[p,r]被划分成两个在数组[p,q-1],[q+1,r] 可能为空
*使得[p,q-1]中的元素都小于a[q],[q+1,r]中的元素都大于a[q]
*下标q在划分过程中计算
*对两个子数组进行递归调用 排序
*/
static void quickSort_routine(int a[],int p,int r){
if(p<r){
int q = partition(a,p,r);
quickSort_routine(a,p,q-1);
quickSort_routine(a,q+1,r);
}
}
void quickSort(int a[],int length){
if(a==NULL||length==1)return;
quickSort_routine(a,0,length-1);
}
/************快速排序 end************/
java语言实现
/************快速排序 start************/
public static void quickSort(int[] a){
quickSort(a,0,a.length-1);
}
/*
*数组a[p,r]被划分成两个在数组[p,q-1],[q+1,r]
*使得[p,q-1]中的元素都小于a[q],[q+1,r]中的元素都大于a[q]
*下标q在划分过程中计算
*对两个子数组进行递归调用
*/
private static void quickSort(int[] a,int p,int r){
if(p<r){
int q = partition(a,p,r);
quickSort(a,p,q-1);
quickSort(a,q+1,r);
}
}
/*
*partition是快速排序的关键过程 ,对子数组a[p,r]就地重排
*选择一个数组元素作为主元,围绕它来划分数组a[p,r],数组被划分为四个区域(可能有空的)
*游标i用于增长比主元的小的元素位置空间,
*游标j用于从左向右依次将元素和主元进行比较
*/
private static int partition(int[] a,int p,int r){
int index = p+ new Random().nextInt(r-p+1);//随机选取主元
Utils.swap(a, index, r);//将随机选取的主元和最后一位的元素互换
int x = a[r];
int i = p-1;//用于移动比主元小的元素游标
for(int j=p;j<r;j++){
if(a[j]<=x){//发现比主元小的元素
Utils.swap(a, ++i, j);//比主元小的元素依次排列(先不分大小)
}
}
Utils.swap(a, ++i, r);//将主元放置在比它小的右边
return i;
}
/************快速排序 end************/