排序算法

来源:互联网 发布:6sigma软件 编辑:程序博客网 时间:2024/06/05 23:47

基础知识介绍

1、稳定排序和非稳定排序     

      简单地说就是所有相等的数经过某种排序方法后,仍能保持它们在排序之前的相对次序,我们就说这种排序方法是稳定的。反之,就是非稳定的。比如:一组数排序前是a1,a2,a3,a4,a5,其中a2=a4,经过某种排序后为a1,a2,a4,a3,a5,则我们说这种排序是稳定的,因为a2排序前在a4的前面,排序后它还是在a4的前面。假如变成a1,a4,a2,a3,a5就不是稳定的了。

2、内排序和外排序

      在排序过程中,所有需要排序的数都在内存,并在内存中调整它们的存储顺序,称为内排序;

      在排序过程中,只有部分数被调入内存,并借助内存调整数在外存中的存放顺序排序方法称为外排序。

3、算法的时间复杂度和空间复杂度

      所谓算法的时间复杂度,是指执行算法所需要的计算工作量。

      一个算法的空间复杂度,一般是指执行这个算法所需要的内存空间。

插入排序

1、直接插入排序(Straight  Insertion  Sort)

       思想:整个记录分为有序区和无序区,依次从无序区取一个记录插入到有序区。下标为0的记录设置为哨兵,用作比较。

       核心代码:

for(int i = 2; i <= n; i++){    iArray[0] = iArray[i];    int j;    for(j = i - 1; iArray[j] > iArray[0]; j--){iArray[j+1] = iArray[j];    }    iArray[j+1] = iArray[0]; }
        时间复杂度:

            (1)最好情况:整个序列为正序,总的比较次数是n-1,移动次数是2(n-1),因此时间复杂度是O(n);

            (2)最坏情况:真个序列为逆序,在第i趟插入时,i需与临界条件以及前面i-1个记录做比较,因此总的比较次数是2+.....+n,总的比较次数是从(n+2)*(n - 1)/2,同时移动次数是3+......+(n+1),总的移动次数是(n+4)*(n-1)/2,因此时间复杂度是O(n^2)。

            (3)平均情况下,各种排列的概率相同,在插入第i个记录时平均所需比较的记录时序列的一半,所以总的比较次数是(n*2)(n-1)/4,移动次数是(n+4)(n-1)/4,因此,时间复杂度是O(n^2)

       空间复杂度:O(1)

       直接插入排序是一种稳定的排序方法

2、希尔排序(Shell  Sort)

       思想:它是对直接插入排序的一种改进,因为当序列基本有序或者记录数较少时,直接插入排序的效率很高。因此希尔排序是将序列分成若干子序列,在子序列内分别进行排序,待整个序列基本有序时,再对整个序列进行直接插入排序。

       核心代码:

int d, i, j;for(d = n / 2; d >= 1; d = d / 2){    //操作与插入排序类似    for(i = d + 1; i <= n; i++){iArray[0] = iArray[i];for(j = i -d; j >= 1 && iArray[j] > iArray[0]; j -= d){    iArray[j+d] = iArray[j];}iArray[j+d] = iArray[0];    }}
       时间复杂度:分析复杂,据前人大量的实验结果得出,时间性能在O(n^2)和O(nlog2(n))之间。当n在某个特定范围时,时间性能约为O(n^1.3)。

       空间复杂度:O(1)

       希尔排序是一种不稳定的排序方法

交换排序

1、冒泡排序(Bubble  Sort)

       思想:序列分为有序区和无序区,在无序区将记录两两比较,如果反序则交换记录,选出的最大记录放在有序区,有序区记录加1,无序区记录减一,循环此过程,直到无序区没有反序为止。

       核心代码:

int i, j, exchagne;exchange = n;while(exchange != 0){    int flag = exchange;    exchangne = 0;    //比较区间为[1,flag]    for(j = 1; j < flag; j++){if(iArray[j] > iArray[j+1]){    iArray[0] = iArray[j];    iArray[j] = iArray[j+1];    iArray[j+1] = iArray[0];    //记录上一趟排序最后的交换位置,避免无用比较    exchange = j;         }    }}
         时间复杂度:

                (1)最好情况下,整个序列为正序,算法只执行一趟,比较了n-1次,不移动记录。时间复杂度是O(n)。

                (2)最坏情况下,整个序列是逆序,总的比较次数是(n-1)+.....+1,因此时间复杂度是(n-1)*n/2,移动次数3*n*(n-1)/2,因此总的时间复杂度是O(n^2)。

                (3)平均情况下,时间复杂度与最坏程度同一等级,O(n^2)

        空间复杂度:O(1)

        冒泡排序是一种稳定的排序算法

2、快速排序(Quick Sort)

        思想:冒泡排序记录的比较和移动是在相邻位置进行的,记录每次只能后移一位,因此总的比较次数和移动次数较多。快速排序,比较和移动是从两端向中间进行的,记录的移动距离较远,减少了总的比较次数和移动次数。它是通过选择一个中间轴值,将序列分为两个独立的部分,左侧均小于或等于轴值,右侧均大于或等于轴值。

        核心代码:

int getLocate(int *iArray, int first, int end){    int i = first, j = end;    while(i < j){        //循环保证i指针永远小于j指针        while(i<j && iArray[i] <= iArray[j]){    j--;}if(i < j){    iArray[0] = iArray[i];    iArray[i] = iArray[j];    iArray[j] = iArray[0];    i++;}while(i<j && iArray[i] <= iArray[j]){    i++;}if(i < j){    iArray[0] = iArray[i];    iArray[i] = iArray[j];           iArray[j] = iArray[0];    j--;}    }    return i;}void quickSort(int *iArray, int first, int end){    if(first < end){        //获取轴值        int location = getLocate(iArray, first, end);        //递归对每个子序列进行交换排序        quickSort(iArray, first, location-1);quickSort(iArray, location+1, end);    }}
        时间复杂度:

               (1)最好情况:O(nlog2(n))。

               (2)最换情况:O(n^2)。

               (3)平均情况:O(nlog2(n))

       空间复杂度:递归使用栈,最好情况下栈的深度为O(log2(n)),最坏情况下为O(n),平均情况下为O(log2(n))

       快速排序时一种不稳定的排序方法。是目前公认的一种比较好的排序算法,是qsort的内部实现。

选择排序

1、简单选择排序

       思想:将序列分为有序区和无序区,在无序区中依次比较,记录最大记录的下标,比较完毕后,将最大记录放入有序区,依次循环。

       核心代码:

int i, j, index;for(i = 1; i <= n; i++){    //index记录最小记录的下标    index = i;    for(j = i + 1; j <= n; j++){if(iArray[j] < iArray[index]){    index = j;}    }    if(index != i){iArray[0] = iArray[i];iArray[i] = iArray[index];iArray[index] = iArray[0];    }}
       时间复杂度:无论序列如何排序,关键码的比较次数都一样,均为(n -1)*n/2,即O(n^2)。这是简单选择排序的最差、最好、平均时间性能。

       空间复杂度:O(1)

       简单选择排序是一种不稳定的排序方法

2、堆排序(Heap  Sort)

       思想:简单选择排序每趟排序都是选出最小记录,没有将之前的比较结果保存起来,因此比较次数较多。堆排序是在选择最小关键码的同时,记录较小关键码。堆排序首先将序列构造成一个堆,此时,选出最大的记录,即堆顶。然后移走堆顶,再将堆调整为大根堆,进行循环,直到堆中只有一个记录为止。

       核心代码:

void adjustHeap(int *iArray, int locate, int n){    int i = locate, j = 2 * locate;    while(j <= n){if(j < n && iArray[j] < iArray[j+1]){        j++;}if(iArray[i] < iArray[j]){    iArray[0] = iArray[j];            iArray[j] = iArray[i];    iArray[i] = iArray[0];    i = j;    j = 2 * i;}else{    break;}    }}void heapSort(int *iArray, int n){    int i, j;    for(i = n /2; i >= 1; i--){adjustHeap(iArray, i, n);    }    for(j = n; j > 1; j--){iArray[0] = iArray[j];iArray[j] = iArray[1];iArray[1] = iArray[0];adjustHeap(iArray, 1, j-1);    }}
       时间复杂度:初建堆需要O(n)时间,第i次取堆顶记录重建堆需要O(log2(i))时间,并且需要取n-1次堆顶记录。因此总的时间复杂度为O(nlog2(n)),这是堆排序的最好、最差、平均情况。

       空间复杂度:O(1)。

       堆排序时一种不稳定的排序方法

归并排序

1、二路归并排序算法(非递归算法)

       思想:将若干有序序列两两归并,直至所有待排序记录都在一个有序序列为止。

       核心代码:

//一次归并void merge(int *iArray, int *iArray1, int startLocate, int endLocate1, int endLocate2){    int k = startLocate;    int i = k, j = endLocate1 + 1;    int m1 = endLocate1, m2 = endLocate2;    //m1:子序列1的结束位置   m2:子序列2的结束位置    while(i <= m1 && j <= m2){if(iArray[i] <= iArray[j]){            iArray1[k++] = iArray[i++]; }else{    iArray1[k++] = iArray[j++];}    }    if(i <= m1){while(i <= m1){    iArray1[k++] = iArray[i++];}    }else{while(j <= m2){    iArray1[k++] = iArray[j++];}   }}/*归并公共存在三种关系:1、当i<n-2*h+1时,存在两个长度相同的子序列,可以进行归并操作2、当i<n-h+1时,存在两个子序列,当其中一个子序列长度不足h,可以进行归并操作。3、当i>=n-h+1时,只有一个子序列。直接复制到数组末尾。*/void mergePass(int *iArray, int *iArray1, int distance, int len){    int i = 1;    int n = len;    int h = distance;    while(i <= n-2*h+1){merge(iArray, iArray1, i, i+h-1, i+2*h-1);i += 2*h;    }    if(i < n-h+1){merge(iArray, iArray1, i, i+h-1, n);    }else{for(int k = i; k <= n; k++){    iArray1[k] = iArray[k];}    }}//定义一个长度为n的数组用来存放归并结果void mergeSort(int *iArray, int *iArray1, int n){    int h = 1;    //保证成对操作,防止当归并趟数为奇数时,结果保存在iArray1中    while(h < n){mergePass(iArray, iArray1, h, n);h = 2*h;        // 查看h值     cout<<h<<endl;        mergePass(iArray1, iArray, h, n);        h = 2*h;               // 查看h值      cout<<h<<endl;    }}
      时间复杂度:一趟归并所需时间为n,整个归并排序时间复杂度是O(log2(n))。因此,总的时间复杂度是O(nlog2(n)),这是归并排序算法最好、最坏、平均的时间性能。

      空间复杂度:O(n)

      二路归并时一种稳定的排序方法。

排序方法比较









0 0
原创粉丝点击