排序算法-09-排序1 排序 (25分)-第一部分

来源:互联网 发布:2016年度网络热词 编辑:程序博客网 时间:2024/05/21 17:44
  • 题目
    09-排序1 排序 (25分)

冒泡排序

  • 大概的思想
    一趟冒泡排序之后,最大的数在最后;第二趟冒泡之后,次大的数在倒数第二位。
    其中一个优化措施是 用一个变量flag标识这一趟冒泡中是否有过交换,如果没有的话,说明数组已经有序了。
  • 代码
#include <stdio.h>#define ElementType long#define maxn 100001ElementType A[maxn];void Swap(ElementType A[], int i, int j);void Bubble_sort(ElementType A[],int N);int main(int argc, const char * argv[]) {    int N;    scanf("%d",&N);    for(int i=0; i<N; i++){        scanf("%ld",&A[i]);    }    Bubble_sort(A, N);    printf("%ld",A[0]);    for(int i=1; i<N; i++){        printf(" %ld",A[i]);    }    printf("\n");    return 0;}void Swap(ElementType A[], int i, int j){    ElementType tmp = A[i];    A[i] = A[j];    A[j] = tmp;}void Bubble_sort(ElementType A[],int N){    int flag,i,j;    for(i=N-2; i>=0; i--){        flag = 0;        for(j=0; j<=i; j++){            if(A[j] > A[j+1]){                Swap(A, j, j+1);                flag = 1;       //标识发生了交换            }        }        if(flag == 0)   break;//全程无交换,说明前面已经全部有序了,不需要再比较    }}
  • 运行结果
    冒泡排序结果
  • 小结
    最坏时间复杂度是O(N^2),如果数组基本有序,那么经过若干趟排序之后数组就可能是有序的,所以可能复杂度是O(N),所以通过了测试点8
    评价:交换排序,简单排序,最坏时间复杂度是O(N^2),在数组基本有序的情况下时间复杂度可能是O(N)。空间复杂度O(1)。

插入排序

  • 大概的思想
    就像自己打牌的时候摸牌一样,一张张摸,然后插入到合适的位置。每次插入后手里的牌都是有序的。
    可以有的一点小优化是不要用Swap函数,不要交换,而是依次挪动数据。
  • 代码
void Insert_sort(ElementType A[],int N){    int i,j;    ElementType tmp;    for(i=1; i<N; i++){        tmp = A[i];         //摸下一张牌        for(j=i-1; j>=0 && tmp<A[j]; j--){            A[j+1] = A[j];      //移出空位        }        A[j+1] = tmp;           //新牌落位    }}
  • 运行结果
    插入排序结果
    这儿虽然全部通过了,但可以看到测试点5、7、9用时很长。由于题目要求是10秒内,才会通过。
  • 小结
    插入排序最坏时间复杂度也是O(N^2),在数组基本有序的情况下,插入牌的时候基本不需要怎么挪动,所以时间复杂度可能降为O(N),空间复杂度O(1)
    冒泡排序和插入排序都是每次交换消除一个逆序对,即每次交换只能交换相邻的数据,所以时间复杂度才为O(N^2),希尔排序就是受此启发,从交换距离较远的两个数据,尽快使得数组基本有序

希尔排序

  • 大概的思想
    类似与插入排序,但是每次比较的不是相邻的数,而是距离为某个值的两个元素。要定义一个间隔序列: 从大到小,最后是1
    如Sedgewick序列:
    int Sedgewick[] = {929, 505, 209, 109, 41, 19, 5, 1, 0};
    这个序列中的元素一般要是互质的,这样希尔排序性能才会并较好。
    我在这儿用一个 3*i+1 这样的序列
  • 代码:
void Shell_sort(ElementType A[],int N){    int dis = 1;    ElementType tmp;    int i,j;    while (dis < N/3) {        dis = dis*3+1;    }    while (dis>=1) {        //将数组变为dis间隔有序        for(i=dis; i<N; i++){            tmp = A[i];            for(j=i; (j>=dis)&&(tmp<A[j-dis]); j-=dis){                A[j] = A[j-dis];            }            A[j] = tmp;        }        dis /= 3;    }}
  • 运行结果
    希尔排序结果
    这儿可看到测试点5、7、9的运行时间比插入排序时间短很多。说明希尔排序是对插入排序的一个很大的改进。
  • 小结
    一般来说,这要这个序列选取的比较好,希尔排序的时间复杂度一定小于O(N^2)。一般在O(N)-O(N^2)之间。

选择排序

  • 大概的思想
    每次选出当前未排序部分中的最小值,然后和未排序部分的第一个元素交换。
    如果就是简单的扫描后面的整个数组,时间复杂度为一定O(N^2)。
  • 代码
void Selection_sort(ElementType A[],int N){    int i,j,minPos;    for(i=0; i<N; i++){        minPos = i;        for(j=i; j<N; j++){            if(A[j]<A[minPos])  minPos = j;        }        if(minPos != i) Swap(A, i, minPos);    }}
  • 运行结果
    简单选择排序运行结果
  • 小结
    因为不管数据时候原来就是有序,时间复杂度始终为O(N^2),所以后面一定会超时。
  • 注意
    选择排序在 求N个数中前K大数这类问题是一种不错的选择。(特别是N>>K 时)
    (1)如果像前面这样简单选择,时间复杂度是O(K*N)。
    (2)一种优化是可以建立一个大小为K的最小堆。首先将前K个数调整为一个最小堆,然后对后面的数,依次与堆顶的数比较,如果大于堆顶的数,则将堆顶替换为该数,调整堆。这样时间复杂度可以降为O(N*logK)。
    (3)另外一种方法是直接利用堆排序。将数组中的N个数直接调整为最大堆(时间:O(N)),然后K次删除堆顶元素并调整(时间: K*logN)。所以总的时间复杂度为O(N+K*logN)。

堆排序

  • 大概的思想
    首先将数组中N个数调整为最大堆(O(N)时间);然后依次把堆顶元素和对应位置的元素交换,然后调整堆(O(N*logN)时间)。总时间复杂度就是O(N*logN)
    这儿需要注意的一个地方是 数组从下标0就存放了元素,那么下标为i的结点,它左孩子的下标是 2*i+1,右孩子的结单是 2*i+2。还有建立堆的时候不是从N/2开始调整,而是从 N/2-1 开始调整。
  • 代码
/* 将N个元素的数组中以A[p]为根的子堆调整为最大堆, 因为A中的元素从下标0开始,所以左孩子的下标是 2*i+1  */void PercDown( ElementType A[], int p, int N ){    int parent,child;    ElementType tmp = A[p];    for(parent=p; (parent*2+1)<N; parent=child){        child = parent*2 + 1;        if(child+1<N && A[child+1]>A[child])    child++;        if(tmp < A[child])    A[parent] = A[child];        else    break;    }    A[parent] = tmp;}void Heap_sort(ElementType A[],int N){    for(int i=N/2-1; i>=0; i--){        PercDown(A, i, N);      //BuildMaxHeap    }    for(int i=N-1; i>0; i--){        Swap(A, 0, i);          //deleteMax        PercDown(A, 0, i);    }}
  • 运行结果
    堆排序结果
  • 小结
    堆排序时间复杂度为O(N*logN)。
    堆排序适合解决的问题有:求N个数中前K大数的这类问题。

归并排序

  • 基本思想
    分而治之的思想。要将一个大数组排序,可以把它分成两部分,先将这两个小一点的数组排好序,然后再把它们归并成一个大的有序数组。
    所以就可以递归的将数组分为两个部分,直到数组大小为1,然后再一步步归并上来。

  • 代码

void Merge(ElementType A[], ElementType tmpA[], int L, int R, int rightEnd){    //printf("L is %d, rightEnd is %d\n",L,rightEnd);    if(L>=R)    return;    int leftEnd = R - 1;    int tmp = L;    //保存结果数组的初始位置    int count = rightEnd-L+1;    while (L<=leftEnd && R<=rightEnd) {        if(A[L] <= A[R])    tmpA[tmp++] = A[L++];        else                tmpA[tmp++] = A[R++];    }    while (L<=leftEnd) {        tmpA[tmp++] = A[L++];    }    while (R<=rightEnd) {        tmpA[tmp++] = A[R++];    }    for(int i=0; i<count; i++){        A[rightEnd-i] = tmpA[rightEnd-i];    }}void Msort(ElementType A[], ElementType tmpA[], int L, int rightEnd){    int center;    if (L < rightEnd) {        center = (L+rightEnd)/2;        Msort(A, tmpA, L, center);        Msort(A, tmpA, center+1, rightEnd);        Merge(A, tmpA, L, center+1, rightEnd);    }}void Merge_sort(ElementType A[],int N){    ElementType *tmpA;    tmpA = malloc(N * sizeof(ElementType));    if(tmpA != NULL){        Msort(A, tmpA, 0, N-1);        free(tmpA);    }else{        printf("空间不足\n");    }}
  • 运行结果
  • 归并排序的结果
    运行结果说明归并排序也很快。
  • 小结
    归并排序,利用分而治之的思想。
    时间大部分都花在归并的时候。归并时,需要O(N)的时间。
    T(N) = 2*T(N/2) + O(N)
    利用主方法,可以求得时间复杂度为 O(N*logN)。
    同时空间复杂度为O(N),用来保存临时数组。
  • 归并排序的优化
    (1)当分解到规模比较小的数组时,可以利用简单排序进行排序(比如插入、冒泡排序等)。
    (2)测试数组是否有序:可以加一个判断条件,如果a[center]小于a[center+1],我们就认为数组已经有序并跳过merge方法
    (3)不要每次都将元素复制到辅助数组,一次排序将输入数组排序到辅助数组,一次将辅助数组排序到输入数组
1 0
原创粉丝点击