归并排序和快速排序

来源:互联网 发布:淘宝信息管理在哪里 编辑:程序博客网 时间:2024/05/20 08:27

分治法

有很多算法在结构上是递归的:为了解决一个给定的问题,算法要一次或多次地递归调用其自身来解决相关的子问题。这些算法通常采用分治策略(divide-and-conquier):将原问题划分成n个规模较小而结构与原问题相似的子问题;递归地解决这些子问题,然后再合并其结果,就得到原问题的解。分治模式在每一层递归上都有三个步骤:

²         分解(divide):将原问题分解成一系列子问题;

²         解决(conquer):递归地解各子问题。若子问题足够小,则直接求解;

²         合并:将子问题的结果合并成原问题的解。


自底向上的归并排序

归并排序算法完全依照分治模式,直观的操作如下:

²         分解:将n个元素分成各含n/2个元素的子序列;

²         解决:用归并排序法对两个子序列递归地排序;

²         合并:合并两个已排序的子序列以得到排序结果。

    观察下面的例子,可以发现:归并排序在分解时,只是单纯地将原问题分解为两个规模减半的子问题;在分解过程中,没有任何对原问题所含信息的利用,没有任何尝试对问题求解的动作;这种分解持续进行,直到子问题规模降足够小(为1),这时子问题直接得解;然后,自底向上地合并子问题的解,这时才真正利用原问题的特定信息,执行求解动作,对元素进行比较。

4 2 5 7 1 2 6 3

4 | 2  |  5 | 7   |   1 | 2  |  6 | 3

4 2 5 7   |   1 2 6 3

2 4  |  5 7   |   1 2  |  3 6

4 2  |  5 7   |   1 2  |  6 3

2 4 5 7   |   1 2 3 6

4 | 2  |  5 | 7   |   1 | 2  |  6 | 3

1 2 2 3 4 5 6 7


这种自底向上分治策略的编程模式如下:

如果问题规模足够小,直接求解,否则

        单纯地分解原问题为规模更小的子问题,并持续这种分解;

        执行求解动作,将子问题的解合并为原问题的解。

    由于在自底向上的归并过程中,每一层需要进行i组n/i次比较,而且由于进行的是单纯的对称分解,总的层数总是lg n,因此,归并排序在各种情况下的时间代价都是Θ(n lg n)。试想,能够加大分组的力度,即每次将原问题分解为大于2的子问题,来降低运行时间?


下面程序的巧妙之处在于,他把两个半个数组全部复制到两个新的数组里面去,每个数组比原来多一个位置,这个多的位置干啥用呢?

用来放置哨兵,所谓的哨兵就是比数组里面明显大的的元素,因此啊在把两个数组向一个大的数组里面去拷贝的时候,不需要控制两个数组的下标,不需要判断是否一个已经越界,剩下哪个数组了,然后全部拷贝进去,会让这个程序的逻辑变得非常简单。


#include<stdio.h>#include<stdlib.h>#define INFINITE 1000//对两个序列进行合并,数组从mid分开//对a[start...mid]和a[mid+1...end]进行合并void merge(int *a,int start,int mid,int end){    int i,j,k;    //申请辅助数组    int *array1=(int *)malloc(sizeof(int)*(mid-start+2));    int *array2=(int *)malloc(sizeof(int)*(end-mid+1));    //把a从mid分开分别赋值给数组    for(i=0; i<mid-start+1; i++)        *(array1+i)=a[start+i];    *(array1+i)=INFINITE;//作为哨兵    for(i=0; i<end-mid; i++)        *(array2+i)=a[i+mid+1];    *(array2+i)=INFINITE;    //有序的归并到数组a中    i=j=0;    for(k=start; k<=end; k++)    {        if(*(array1+i) > *(array2+j))        {            a[k]=*(array2+j);            j++;        }        else        {            a[k]=*(array1+i);            i++;        }    }    free(array1);    free(array2);}//归并排序void mergeSort(int *a,int start,int end){    int mid=(start+end)/2;    if(start<end)    {        //分解        mergeSort(a,start,mid);        mergeSort(a,mid+1,end);        //合并        merge(a,start,mid,end);    }}int main(){    int i;    int a[7]= {0,3,5,8,9,1,2}; //不考虑a[0]    mergeSort(a,1,6);    for(i=1; i<=6; i++)        printf("%-4d",a[i]);    printf("\n");    return 1;}




下面这个程序是我自己写的:里面当时出现了好多错误,已经用注释表明了。

#include <iostream>#include <stdlib.h>#include <stdio.h>#include <string.h>#include<time.h>#define random(x) (rand()%x)using namespace std;void MergeSort( int* a, int start, int end);void  merge(int *a,int start,int mid,int end);void printArray(int a[],int n){    for(int i =0; i<n; i++)    {        cout<< a[i]<<" ";    }}int main(){    while(true){     system("cls");    time_t t;    int a[10]= {0};    srand((unsigned) time(&t));    for(int i =0; i<=9; i++)    {        a[i]= random(100);    }    //int a[10]={6,2,3,8,4,1,7,9,0,5};    cout<<"原数组为:"<<endl;    printArray(a,10);    MergeSort(a,0,9);    cout<<endl<<"数组排序后为:"<<endl;    printArray(a,10);    //int a[]  = {28,28,18};    //merge(a,0,1,2);    //printArray(a,3);    cin.get();    }    return 0;}void MergeSort( int* a, int start, int end){    if(start < end)    {        //内外不影响的        int mid = (start + end )/2;        MergeSort(a,start,mid);        MergeSort(a,mid+1,end);        merge(a,start,mid,end);    }}void merge(int *a,int start,int mid,int end){    int low,high;    low = start;    high = mid + 1;    int *temp= new int[end - start + 1];    int * ptr = temp;    memset(temp,0,sizeof(temp));    while( (low<mid+1) && (high < end +1) )    {        if (a[low]< a[high])        {            *ptr = a[low];            low ++;            ptr++;        }        else        {            // *temp++ = a[high++];            *ptr = a[high];            high++;            ptr++;        }    }    if (low > mid)    {        while(high < end +1)        {            *ptr++ = a[high++];        }    }    else if (high > end)    {        //竟然忘记用循环了        while(low<mid +1)        //有疑问        *ptr++ = a[low++];    }    for(int j =start; j<=end; j++)        //竟然忘记temp参数从零开始        a[j] = temp[j-start];    delete []temp;}



自顶向下的快速排序

快速排序也是基于分治策略,它的三个步骤如下:

²         分解:数组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]排序;

²         合并:因为两个子数组是就地排序的,将它们的合并并不需要操作,整个A[p..r]已排序。

    可以看到:快速排序与归并排序不同,对原问题进行单纯的对称分解;其求解动作在分解子问题开始前进行,而问题的分解基于原问题本身包含的信息;然后,自顶向下地递归求解每个子问题。可以通过下面的例子,观察快速排序的执行过程。由于在快速排序过程中存在不是基于比较的位置交换,因此,快速排序是不稳定的。

4 2 5 7 1 2 6 | 3

2 1 2   |   3   |   7 4 5 6

1  |  2  |  2   |   3   |   4 5  |  6  |  7

1  |  2  |  2   |   3   |   4 | 5  |  6  |  7

这种自顶向下分治策略的编程模式如下:

如果问题规模足够小,直接求解,否则

        执行求解动作,将原问题分解为规模更小的子问题;

        递归地求解每个子问题;

        因为求解动作在分解之前进行,在对每个子问题求解之后,不需要合并过程。

    快速排序的运行时间与分解是否对称有关,而后者又与选择了哪一个元素来进行划分有关。如果划分是对称的,则运行时间与归并排序相同,为Θ(n lg n)。如果每次分解都形成规模为n-1和0的两个子问题,快速排序的运行时间将变为Θ(n2)。快速排序的平均情况运行时间与其最佳情况相同,为Θ(n lg n)。



原创粉丝点击