8种排序算法比较

8种排序算法比较

排序算法有以下8种:
1. 直接插入排序
2. shell排序
3. 直接选择排序
4. 堆排序
5. 冒泡排序
6. 快速排序
7. 归并排序
8. 基数排序

分别从用到的思想,时间复杂度,空间复杂度,稳定性,内部或外部排序,原地排序对比分析。
8种排序算法对比

推荐一个挺好的排序网站:http://www.sorting-algorithms.com

前言

先介绍下时间复杂度的定义和求法:
时间复杂度的定义:
一般情况下,算法中基本操作重复执行的次数是问题规模n的某个函数,用T(n)表示,若有某个辅助函数f(n),使得当n趋近于无穷大时,T(n)/f(n)的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作T(n)=O(f(n)),称O(f(n))为算法的渐进时间复杂度(O是数量级的符号 ),简称时间复杂度。

根据定义,可以归纳出基本的计算步骤
1. 计算出基本操作的执行次数T(n)
基本操作即算法中的每条语句(以;号作为分割),语句的执行次数也叫做语句的频度。在做算法分析时,一般默认为考虑最坏的情况。
2. 计算出T(n)的数量级
求T(n)的数量级,只要将T(n)进行如下一些操作:
忽略常量、低次幂和最高次幂的系数
令f(n)=T(n)的数量级。
3. 用大O来表示时间复杂度
当n趋近于无穷大时,如果lim(T(n)/f(n))的值为不等于0的常数,则称f(n)是T(n)的同数量级函数。记作T(n)=O(f(n))。

1. 直接插入排序

如抓扑克牌一样,抓牌过来一个一个插入,不同是人脑会自动判别,电脑需要一个一个比较才能找到应该插入的位置,从后往前比较,因为比较过程需要移动,从前往后比较会覆盖。

算法思想:每趟将一个待排序的关键字,按照其关键字值的大小插入到已经排好的部分序列的适当位置上,直到插入完成。
代码实现如下:

class Sort {
public:
    int direc_insert(vector<int> &ivec) {
        int len = ivec.size();
        int i, j;
        for (i = 1; i < len; i ++) {
            int temp = ivec[i];
            j = i - 1;
            /* 和temp比较,不是ivec[i],原值会被覆盖 */
            while (j >= 0 && ivec[j] > temp) {
                /* 基本操作 */
                ivec[j + 1] = ivec[j];
                -- j;
            }
            ivec[j + 1] = temp;
        }
        return 0;
    }
};

一般不特别说明,讨论的时间复杂度均是最坏情况下的时间复杂度。
时间复杂度:内层的循环是基本操作,考虑最坏的情况下,基本操作执行n *(n + 1) / 2次,其时间复杂度是O(N^2),考虑最好的情况下,数据有序,则时间复杂度是O(N);
空间复杂度:所需的额外空间只有一个temp,空间复杂度是O(1);
稳定性:如果相等,直接插入后面,稳定排序;
直接插入排序属于内部排序。

2. 希尔排序

希尔排序也属于插入排序。
算法思想:希尔排序又叫做缩小增量排序,是将待排序的序列按某种规则分成几个子序列,分别对这几个子序列进行插入排序,其中这一规则就是增量。如可以使用增量5、3、1来分格序列,且每一趟希尔排序的增量都是逐渐缩小的,希尔排序的每趟排序都会使得整个序列变得更加有序,等整个序列基本有序了,再使用一趟插入排序,这样会更有效率,这就是希尔排序的思想。

代码实现1:

int shell_sort(vector<int> &ivec) {
        int len = ivec.size();
        int gap, i, j;
        for (gap = len >> 1; gap > 0; gap >>= 1)
            for (i = gap; i < len; i += gap) {
                int temp = ivec[i];
                j = i - gap;
                while (j >= 0 && ivec[j] > temp) {
                    ivec[j + gap] = ivec[j];
                    j -= gap;
                }
                ivec[j + gap] = temp;
            }
        return 0;
    }

思路容易理解,先分别做直接插入排序,基本有序后再做直接插入排序,因为基本有序后直接插入排序的效率很高,复杂度是O(N)。
改进的代码实现2:

void shellsort3(int a[], int n)
{
    int i, j, gap;

    for (gap = n / 2; gap > 0; gap /= 2)
        for (i = gap; i < n; i++)
            for (j = i - gap; j >= 0 && a[j] > a[j + gap]; j -= gap)
                Swap(a[j], a[j + gap]);
}

时间复杂度:希尔排序的时间复杂度平均情况为O(nlogn);
空间复杂度:O(1);
稳定性:不稳定
注意:希尔排序的增量取法,首先增量序列的最后一个值一定是1,其次增量序列中的值没有除1之外的公因子,如增量序列不要是8,4,2,1(有公因子2),会有重复排序。

3. 直接选择排序

算法思想:该算法的主要动作就是“选择”,采用简单的选择方式,从头至尾顺序扫描序列,找出最小的一个记录,和第一个记录交换,接着从剩下的记录中继续这种选择和交换,最终使序列有序。

    int direc_select(vector<int> &ivec) {
        int len = ivec.size();
        int i, j, min;
        for (i = 0; i < len; i ++) {
            min = i;
            for (j = i + 1; j < len; j ++) {
                if (ivec[j] < ivec[min])
                    min = j;
            }
            swap(ivec[i], ivec[min]);
        }
        return 0;
    }

时间复杂度:将最内层循环中的比较视为基本操作,其执行次数为(n-1+1)*(n-1)/2=n(n-1)/2,不管输入数组有序还是无序,比较次数是不会减少的,所以最坏、平均、最好的时间复杂度是O(N^2),与初始序列无关;
空间复杂度:额外空间只有一个temp,因此空间复杂度为O(1);
稳定性:不稳定,如:(7) 2 5 9 3 4 [7] 1,(7)和1交换后,两个7的相对顺序换了;
适用场景:小规模时,选择排序的效率比较高。

4. 堆排序

堆排序属于一种选择排序。
定义:
堆是一棵完全二叉树。
最大堆:是指根节点的关键字值是堆中的最大关键字值,且每个节点若有儿子节点,其关键字值都不小于其儿子节点的关键字值。
最小堆:是指根节点的关键字值是堆中的最小关键字值,且每个节点若有儿子节点,其关键字值都不大于其儿子节点的关键字值。

堆排序的过程就是构建最大堆或最小堆的过程。最关键的操作是将序列调整为堆,整个排序的过程就是通过不断调整使得不符合堆定义的完全二叉树变为符合堆定义的完全二叉树的过程。
堆排序,参考了http://segmentfault.com/a/1190000002466215

   /* 
     * adjust the ith node tree to be a maximum heap
     * index starts from 0
     * ith node is not leaf node
     */
    void AdjustHeap(vector<int> &ivec, int i, int len) {
        /* left children */
        int child = 2 * i + 1;
        int curParent = ivec[i];
        while (child < len) {
            if (child + 1 < len && ivec[child] < ivec[child + 1])
                /* the index of the bigger children */
                ++ child;
            if (ivec[child] > curParent) {
                /* 
                 * do not set ivec[child] = curParent, because swaping
                 * the parent and children may lead to the result that 
                 * the children tree is not maximum_heap any more.
                 * so we don't assign temporarily. 
                 */
                ivec[i] = ivec[child];
                i = child;
                child = child * 2 + 1;
            } else {
                break;
            }
        }
        /* now we assign ivec[i] = curParent */
        ivec[i] = curParent;
    }
void HeapSort(vector<int> &ivec) {
        int len = ivec.size();
        int i;
        /*
         * create maximum heap from the first not lead node
         */
        for (i = len / 2 - 1; i >= 0; i--) {
            AdjustHeap(ivec, i, len);
        }
        /*
         * heap sort
         * i is the number of heap sort every time
         * every time heap sort number is not all
         */
        for (i = len - 1; i > 0; i--) {
            int max = ivec[0];
            ivec[0] = ivec[i];
            ivec[i] = max;
            AdjustHeap(ivec, 0, i);
        }
    }

堆排序的主要过程是建立初始堆和反复调整堆,最大堆排序的结果是从小到大的顺序。
时间复杂度:和基本操作的执行次数,完全二叉树的高度为[log(n+1)],即对每个节点调整的时间复杂度为O(logn),基本操作总次数是两个并列循环中基本操作次数相加,则整个算法时间复杂度为O(logn)n/2+O(logn)(n-1),即O(nlogn);
空间复杂度:额外空间只有一个curParent,因此空间复杂度为O(1);
稳定性:稳定不稳定,给个例子最能说明,1,2,2,堆排序后2的相对位置发生变化;

5. 冒泡排序

冒泡排序是基于交换的排序。

void BubbleSort(vector<int> &ivec) {
        int len = ivec.size();
        int i, j;
        for (i = len - 1; i > 0; i --)
            for (j = 0; j < i; j ++)
                if (ivec[j] > ivec[j + 1])
                    swap(ivec[j], ivec[j + 1]);
    }

时间复杂度:最坏的情况下,比较次数O(N^2),交换次数是(n - 1) * n / 2,时间复杂度是O(N^2);最好的情况下,比较次数O(N),交换次数0,时间复杂度O(N);平均情况下,时间复杂O(N^2);
空间复杂度:O(1);
稳定性:稳定;

6. 快速排序

本人此篇博文有叙述快速排序和归并排序:
http://blog.csdn.net/willinux20130812/article/details/47010513
快速排序用的思想是分治,将大问题分为两个子问题,再递归解决两个子问题。

int Partition(vector<int> &ivec, int left, int right) {
        int pivot_key = ivec[left];
        while (left < right) {
            while (left < right && ivec[right] >= pivot_key)
                -- right;
            ivec[left] = ivec[right];
            while (left < right && ivec[left] < pivot_key)
                ++ left;
            ivec[right] = ivec[left];
        }
        ivec[left] = pivot_key;
        return left;
    }
    void QuickSort(vector<int> &ivec, int left, int right) {
        if (left < right) {
            int pos = Partition(ivec, left, right);
            QuickSort(ivec, left, pos - 1);
            QuickSort(ivec, pos + 1, right);
        }
    }

按照严奶奶《数据结构》课本上写的,递归实现。
时间复杂度:最好情况下时间复杂度为O(nlogn),待排序列越接近无序,则该算法效率越高,在最坏情况下时间复杂度为O(n*n),待排序列越接近有序,则该算法效率越低,算法的平均时间复杂度O(nlogn)。就平均时间而言,快速排序是所有排序算法中最好的;
空间复杂度:O(logn),快速排序是递归进行的,需要栈的辅助,因此需要的辅助空间比前面几类排序方法要多;
稳定性:快速排序不稳定,如果两个相等的值都小于枢轴值,那么交换后两个相等值的相对位置发生变化,如8,5,5;

7. 归并排序

归并排序和快速排序过程很像的,二者都是分治的基本思想。
归并排序算法完全遵循分治模式。直观上其操作如下:
分解:分解待排序的n个元素的序列成各具有n/2个元素的两个子序列。
解决:使用归并排序递归地排序两个子序列。
合并:合并两个已排序的子序列以产生已排序的答案。
过程:将待排序的n个元素的序列,分为两个长度n/2的子序列,两个子序列递归调用归并排序,排序好后,再合并两个子序列。

void MergeArray(vector<int> &ivec, int left, int mid, int right, vector<int> &temp) {
        int rstart = mid + 1;
        int lstart = left;
        int index = 0;
        int mov;
        while (lstart <= mid && rstart <= right) {
            if (ivec[lstart] < ivec[rstart])
                temp[index ++] = ivec[lstart ++];
            else
                temp[index ++] = ivec[rstart ++];
        }
        while (lstart <= mid)
            temp[index ++] = ivec[lstart ++];
        while (rstart <= right)
            temp[index ++] = ivec[rstart ++];
        /* 注意起始下标left + mov,细心别写错 */
        for (mov = 0; mov < index; mov ++)
            ivec[left + mov] = temp[mov];
    }
    void MergeSort(vector<int> &ivec, int left, int right, vector<int> &temp) {
        if (left < right) {
            int mid = (left + right) / 2;
            MergeSort(ivec, left, mid, temp);
            MergeSort(ivec, mid + 1, right, temp);
            MergeArray(ivec, left, mid, right, temp);
        }
    }

时间复杂度:共需要进行logn趟排序,每趟排序执行n次基本操作,因此整个归并排序中总的基本操作执行次数为nlogn,所以时间复杂度为O(nlogn),最坏、平均、最好时间复杂度都是O(nlogn),时间复杂度和初始序列无关;
空间复杂度:归并排序需要转存整个待排序列,因此空间复杂度为O(n);
算法导论上有归并排序空间复杂度O(1)的实现
稳定性:稳定

8. 基数排序

基数排序先不写吧,用的比较少。

9. 一些结论

别人总结的结论,我是不想记的,慢慢分析就能分析出来。
(1)快速排序、希尔排序、归并排序、堆排序的平均时间为O(nlogn),其他的为O(n*n)。
(2)快速排序、希尔排序、选择排序、堆排序不稳定,其他的稳定。
(3)经过一趟排序能够保证一个元素到达最终位置的是冒泡排序、快速排序、选择排序、堆排序。
(4)元素比较次数和原始序列无关的是选择排序、折半插入排序。
(5)排序趟数和原始序列有关的是交换类排序。
(6)直接插入排序和折半插入排序的区别是寻找插入位置的方式不同,一个是按顺序查找方式,另一个是按折半查找方式。
参考:
[1] http://blog.csdn.net/hr10707020217/article/details/10581371
[2] http://www.cnblogs.com/twobin/p/3378302.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值