冒泡排序
(一)概念及实现
冒泡排序的原理:重复的遍历要排序的数组,每次遍历过程中从头至尾比较两个相邻的元素,若顺序错误则交换两个元素。
具体如下(实现为升序):
设数组为a[0…n]。
实现代码:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | publicstaticvoidSort<T>(IList<T> arr) whereT : IComparable<T>{    if(arr == null)        thrownewArgumentNullException("arr");    intlength = arr.Count();    if(length > 1)    {        boolisSorted = false;        // 循环n-2次,每循环完一次,冒泡的一个最大值        for(inti = 0; i < length - 1; i++)        {            // 如果一次循环没有发生交换,则说明剩余的元素是有序的。            isSorted = true;            for(intj = 1; j <= length - 1 - i; j++)            {                // 相邻两个元素比较,将较大的交换到右边arr[j]中                if(arr[j - 1].CompareTo(arr[j]) > 0)                {                    isSorted = false;                    Swap(arr, j - 1, j);                }            }            if(isSorted)                break;        }    }}/// <summary>/// 数组元素交换/// </summary>/// <typeparam name="T"></typeparam>/// <param name="arr">数组</param>/// <param name="i">交换元素1</param>/// <param name="j">交换元素2</param>staticvoidSwap<T>(IList<T> arr, inti, intj){    T temp = arr[i];    arr[i] = arr[j];    arr[j] = temp;} | 
示例:
89,-7,999,-89,7,0,-888,7,-7
排序的过程:
-7 89 -89 7 0 -888 7 -7 [999]
-7 -89 7 0 -888 7 -7 [89] 999
……
……
-888 [-89] -7 -7 0 7 7 89 999
(二)算法复杂度
冒泡排序耗时的操作有:比较 + 交换(每次交换两次赋值)。时间复杂度如下:
1) 最好情况:序列是升序排列,在这种情况下,需要进行的比较操作为(n-1)次。交换操作为0次。即O(n)
2) 最坏情况:序列是降序排列,那么此时需要进行的比较共有n(n-1)/2次。交换操作数和比较操作数一样。即O(n^2)
3) 渐进时间复杂度(平均时间复杂度):O(n^2)
从实现原理可知,冒泡排序是在原输入数组上进行比较交换的(称“就地排序”),所需开辟的辅助空间跟输入数组规模无关,所以空间复杂度为:O(1)
(三)稳定性
冒泡排序是稳定的,不会改变相同元素的相对顺序。
(四)优化改进
快速排序
(一)概念及实现
思想:分治策略。
快速排序的原理:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序。
"保证列表的前半部分都小于后半部分"就使得前半部分的任何一个数从此以后都不再跟后半部分的数进行比较了,大大减少了数字间的比较次数。
具体如下(实现为升序):
设数组为a[0…n]。
实现代码:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | /// <summary>/// 快速排序/// </summary>/// <param name="arr">待排序数组</param>/// <param name="left">左边界索引</param>/// <param name="right">右边界索引</param>/// <param name="SelectPivot">选择基准元素委托,并且将基元素与左边界元素交换</param>privatestaticvoidDoSort<T>(IList<T> arr, intleft, intright    , Action<IList<T>, int, int> SelectPivot) whereT : IComparable<T>{    count++;    if(left < right)    {        // 选择基准元素委托        if(SelectPivot != null)            SelectPivot(arr, left, right);        // 临时存储基准元素,以让其他数组元素来填充该位置        T pivot = arr[left];        intlow = left;        inthigh = right;        while(low < high)        {            // 从右向左找第一个小于 基数 的数              while(low < high && arr[high].CompareTo(pivot) >= 0)                high--;            if(low < high)                arr[low++] = arr[high];            // 从左向右找第一个大于等于 基数 的数            while(low < high && arr[low].CompareTo(pivot) < 0)                low++;            if(low < high)                arr[high--] = arr[low];        }        arr[low] = pivot;   // 此时 low = high        // 两个分区递归。基准元素无需在参与下一次快速排序        // 加入if判断可以减少50%-55%的递归次数        if(left < low - 1)            DoSort<T>(arr, left, low - 1, SelectPivot);        if(low + 1 < right)            DoSort<T>(arr, low + 1, right, SelectPivot);    }} | 
特别注意在递归前的判断语句,他减少了50%-55%的递归次数。
示例:
89,-7,999,-89,7,0,-888,7,-7
排序的过程:
(以数组第一个元素做为基准值。蓝色代表被分区出来的子数组,绿色代表本次分区基准值)
 
(二)算法复杂度
快速排序耗时的操作有:比较 + 交换(每次交换两次赋值)。时间复杂度如下:
1) 最好情况:选择的基准值刚好是中间值,分区后两分区包含元素个数接近相等。因为,总共经过x次分区,根据2^x<=n得出x=log2n,每次分区需用n-1个元素与基准比较。所以O(nlog2n)
2) 最坏情况:每次分区后,只有一个分区包含除基准元素之外的元素。这样就和冒泡排序一样慢,需n(n-1)/2次比较。即O(n^2)
3) 渐进时间复杂度(平均时间复杂度):O(nlog2n)
从实现原理可知,快速排序是在原输入数组上进行比较分区的(称“就地排序”),所需开辟的辅助空间跟输入数组规模无关,所以空间复杂度为:O(1)
(三)稳定性
快速是不稳定的,会改变相同元素的相对顺序。如示例,以第一个基准89排序时,首先将最后一个元素-7移到了第一个分区的第一个位置上。改变了与第二个-7的相对顺序。
(四)优化改进
当每次分区后,两个分区的元素个数相近时,效率最高。所以找一个比较有代表性的基准值就是关键。通常会采取如下方式:
根据改进方案,写了如下基准值选取委托:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | staticRandom random = null;publicstaticvoidSort<T>(IList<T> arr, QuickType quickType = QuickType.Normal) whereT : IComparable<T>{    if(arr == null)        thrownewArgumentNullException("arr");    switch(quickType)    {        caseQuickType.Normal:            {                DoSort<T>(arr, 0, arr.Count() - 1,                    (subArr, left, right) =>                    {                        // 默认以第1个数为基数。                        // 所以,什么都不用做                    }                    );            }            break;        caseQuickType.Random:  // 随机快排            {                DoSort<T>(arr, 0, arr.Count() - 1,                    (subArr, left, right) =>                    {                        // 2个元素就取默认第一个元素                        if((right - left + 1) > 2)                             {                            // 随机化快排:随机数组中一个元素做基准                            if(random == null)                                random = newRandom(newGuid().GetHashCode());                            intindex = random.Next(left, right);                            T temp = subArr[left];                            subArr[left] = subArr[index];                            subArr[index] = temp;                        }                    }                    );            }            break;        caseQuickType.Balance: // 平衡快排            {                DoSort<T>(arr, 0, arr.Count() - 1,                    (subArr, left, right) =>                    {                        // 2个元素就取默认第一个元素                        if((right - left + 1) > 2)                             {                            intindex = -1;                            // 平衡快排:取开头、结尾、中间3个数据,通过比较选出其中的中值                            intmiddle = (left + right) / 2;                            intmaxIndex = -1;                            for(inti = 0; i <= 1; i++)                            {                                if(i == 0) // 找最大值                                {                                    if(subArr[middle].CompareTo(subArr[left]) >= 0)                                    {                                        maxIndex = middle;                                    }                                    else                                    {                                        maxIndex = left;                                    }                                    if(subArr[maxIndex].CompareTo(subArr[right]) >= 0)                                    {                                        // maxIndex本身为最大值                                    }                                    else                                    {                                        maxIndex = right;                                    }                                }                                if(i == 1) // 找第二大值                                {                                    if(maxIndex == left)                                    {                                        if(subArr[middle].CompareTo(subArr[right]) >= 0)                                            index = middle;                                        else                                            index = right;                                    }                                    elseif(maxIndex == middle)                                    {                                        if(subArr[left].CompareTo(subArr[right]) >= 0)                                            index = left;                                        else                                            index = right;                                    }                                    elseif(maxIndex == right)                                    {                                        if(subArr[middle].CompareTo(subArr[left]) >= 0)                                            index = middle;                                        else                                            index = left;                                    }                                }                            }                            // 交换                            T temp = subArr[left];                            subArr[left] = subArr[index];                            subArr[index] = temp;                        }                    }                    );            }            break;    }} | 
性能测试
测试步骤:
共测试 10*20 次,长度为5000的数组排序
参数说明:
(Time Elapsed:所耗时间。CPU Cycles:CPU时钟周期。Gen0+Gen1+Gen2:垃圾回收器的3个代各自的回收次数)
 

随机快排和平衡快排都比较稳定高效。顺序率约高,平衡快排的优势越明显。
原文:http://www.cnblogs.com/haodayikeshu/p/5115310.html