归并排序和快速排序

分治法

有很多算法在结构上是递归的:为了解决一个给定的问题,算法要一次或多次地递归调用其自身来解决相关的子问题。这些算法通常采用分治策略(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)。



你可能感兴趣的:(归并排序和快速排序)