常用排序算法分析

来源:互联网 发布:2015年网络大电影票房 编辑:程序博客网 时间:2024/05/21 03:28
常用排序算法分析

  • 常用排序算法有冒泡排序,选择排序,插入排序,堆排,希尔排序和快排;
  • 本文尽可能详尽地分析每一种排序算法的定义,实现代码,算法时间复杂度和稳定性。

1.冒泡排序(BubbleSort)
  • 定义:冒泡排序,是模拟气泡上升过程的排序。气泡在上升过程体积不断变大,相似与排序比较中,相邻的两个“气泡”比较大小,大的“气泡”换到后面,一次排序后最大的“气泡”已经到达最后面,这样下次排序就可以不用再处理,只处理剩余的“气泡”,重复N-1次排序(要排序的数量N),完成排序。
  • 实现算法(C#):
    static void BubbleSort(int[] array)
    {
    int n = array.Length; //数组的长度
    for (int i = 0; i < n - 1; i++)
    for (int j = 0; j < n - 1 - i; j++)
    if (array[j] > array[j + 1])
    {
    int temp = array[j];
    array[j] = array[j + 1];
    array[j + 1] = temp;
    }
    }

  • 时间复杂度:第一个for循环执行了n次,第二个for循环每次执行(n-1-i)次,一共n次,总共是  n*(n-1-i)次,根据时间复杂度算法,取最高次幂去系数,O(n2)
  • 稳定性:由于算法中比较大小冒泡排序就是把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,如果两个元素相等,我想你是不会再无聊地把他们俩交换一下的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。
2.选择排序(SelectionSort)
  • 定义:首先在未排序序列中找到最小元素,记录索引值,偏离完毕,将索引值所指元素与第一个元素交换,然后,再从剩余未排序元素中继续寻找最小元素,记录索引值,取索引值所指元素与第二个元素交换。以此类推,直到所有元素均排序完毕。
如序列56,12, 80, 91, 20

第2趟:12 20 80 91 56

第3趟:12 20 56 91 80

第4趟:12 20 56 80 91

  • 实现算法(C#):
  •         static void SelectionSort(int[] array)        {            int n = array.Length;  //数组的长度            int min_index  ;       //记录遍历数组时最小值的下标            for (int i = 0; i < n - 1; i++)            {                min_index = i;                for (int j = i + 1; j <= n - 1; j++)                {                    if (array[j] < array[min_index])                        min_index = j;                }                if (i != min_index)   //说明存在比i所指元素更小的元素,否则i==min_index                {                    int temp = array[i];                    array[i] = array[min_index];                    array[min_index] = temp;                }            }        }
  • 时间复杂度:两个for循环,第一个for循环执行了n次,第二个for循环,每次执行n-1-i次,循环n次,一共进行了(n-1+n-2+...+1)次,根据      时间复杂度算法,取最高次幂去系数,O(n2).
  • 稳定性:假设遇到相同元素,由于在前面的元素先被获取,比较过程中相同元素是不会交换的。所以相同元素的前后顺序并没有改变,所以选择排序是一种稳定排序算法.

3.插入排序(Insert)
  • 定义:插入即表示将一个新的数据插入到一个有序数组中,并继续保持有序。例如有一个长度为N的无序数组,进行N-1次的插入即能完成排序;第一次,数组第1个数认为是有序的数组,将数组第二个元素插入仅有1个有序的数组中;第二次,数组前两个元素组成有序的数组,将数组第三个元素插入由两个元素构成的有序数组中......第N-1次,数组前N-1个元素组成有序的数组,将数组的第N个元素插入由N-1个元素构成的有序数组中,则完成了整个插入排序
  • 实现代码(C#):
        static void InsertSort(int[] array)        {            int n = array.Length;    //数组长度            for (int i = 1; i < n; i++)            {                int insert_num = array[i];                int j;                for (j = i; j > 0 && array[j-1] > insert_num ; j--)                {                    array[j] = array[j-1];   //从排序好的序列后面开始比较,将比插入元素大的元素向后挪                }                //循环最后一次赋值剩余array[j-1],经过自减后 j 就是最后一个未赋值元素的坐标                    array[j] = insert_num;               }        }

  • 时间复杂度:如果目标是把n个元素的序列升序排列,那么采用插入排序存在最好情况和最坏情况。最好情况就是,序列已经是升序排列了,在这种情况下,需要进行的比较操作需(n-1)次即可。最坏情况就是,序列是降序排列,那么此时需要进行的比较共有n(n-1)/2次。插入排序的赋值操作是比较操作的次数加上 (n-1)次。平均来说插入排序算法的时间复杂度为O(n2
  • 稳定性:插入元素是从原先有序的序列后面开始比较,当遇到相同的元素时,也只会把新元素插入到相同元素的后面,不会影响两者的先后顺序,所以插入排序是稳定的算法。

4.希尔排序(ShellSort)
  • 定义:希尔排序是插入排序的一种。是针对插入排序算法的改进。该方法又称缩小增量排序。先取一个小于n的整数d1作为第一个增量,把文件的全部记录分组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量dt=1,即所有记录放在同一组中进行直接插入排序为止。
  • 例如,假设有这样一组数[ 13 14 94 33 82 25 59 94 65 23 45 27 73 25 39 10 ],如果我们以步长为5开始进行排序,我们可以通过将这列表放在有5列的表中来更好地描述算法,这样他们就应该看起来是这样:
  • 13 14 94 33 8225 59 94 65 2345 27 73 25 3910

    然后我们对每列(每一竖,因为刚好到第5个)进行排序:

    10 14 73 25 2313 27 94 33 3925 59 94 65 8245

    将上述四行数字,依序接在一起时我们得到:[ 10 14 73 25 23 13 27 94 33 39 25 59 94 65 82 45 ].

    这时10已经移至正确位置了,然后再以3为步长进行排序:

    10 14 7325 23 1327 94 3339 25 5994 65 8245

    排序之后变为:

    10 14 1325 23 3327 25 5939 65 7345 94 8294

    最后以1步长进行排序(此时就是简单的插入排序了)。

  • 实现代码:
  •         static void ShellSort(int[] array)        {            int n = array.Length;  //数组长度            for (int k = n / 2; k >= 1; k/=2)            {                for (int i = k; i < n; i += k)                {                    int insert_num = array[i];                    int j;                    for (j = i; j > 0 && array[j - k] > insert_num; j -= k)                    {                        array[j] = array[j - k];                    }                    //循环最后一次赋值剩余array[j-1],经过 j-=k 后就是最后一个未赋值元素的坐标                    array[j] = insert_num;                }            }        }
  • 时间复杂度:平均时间复杂度:希尔排序的时间复杂度和其增量序列有关系,这涉及到数学上尚未解决的难题(我承认自己不懂=_=);不过在某些序列中复杂度可以为O(n^1.3)。
  • 稳定性:由于增量的原因,序列中元素并不是相邻比较,所以会出现不稳定的现象。举个简单的栗子;3,1,1,4,初始增量为2(数量的一半),分成3,1 和 1,4两组,各自排序再组合结果就会变成1,1,3,4


5.快速排序(QuickSort)
  • 定义快速排序是对冒泡排序的一种本质改进。它的基本思想是通过一趟扫描后,使得排序序列的长度能大幅度地减少。在冒泡排序中,一次扫描只能确保最大数值的数移到正确位置,而待排序序列的长度可能只减少1。快速排序通过一趟扫描,就能确保某个数(以它为基准点吧)的左边各数都比它小,右边各数都比它大。然后又用同样的方法处理它左右两边的数(分治法),直到基准点的左右只有一个元素为止。
  • 实现代码
  •         static void QuickSort(int[] array, int left, int right)        {            int tag;            if (left < right)            {                tag = Partition(array, left, right);                QuickSort(array, left, tag - 1);                QuickSort(array, tag + 1, right);            }        }        static int Partition(int[] array, int left, int right)        {            int temp = array[left];            while (left < right)            {                while (left < right && array[right] >= temp)   //直到找到比基值小的数                    right--;                if (left < right)                { array[left] = array[right];}                while (left < right && array[left] <= temp)     //直到找到比基值大的数                    left++;                if (left < right)                {  array[right] = array[left];}            }            array[left] = temp;            return left;        }

  • 时间复杂度:假设有1到8代表要排序的数,快速排序会递归log(8)=3次,每次对n个数进行一次处理,所以他的时间复杂度为n*log(n)。
  • 稳定性:由于算法中从后面开始找比基数小的数,存在不稳定的现象。在元素交换的时候,很有可能把前面的元素的稳定性打乱,比如序列为 5 3  3  6, 现在基数元素5和3交换就会把元素3的稳定性打乱,所以快速排序是一个不稳定的排序算法,不稳定发生在元素交换的时刻


5.堆排序(HeapSort)
  • 定义:堆分为最大堆和最小堆,其实就是完全二叉树。最大堆要求节点的元素都要大于其孩子,最小堆要求节点元素都小于其左右孩子,两者对左右孩子的大小关系不做任何要求有了上面的定义,我们可以得知,处于最大堆的根节点的元素一定是这个堆中的最大值。其实我们的堆排序算法就是抓住了堆的这一特点,每次都取堆顶的元素,将其放在序列最后面,然后将剩余的元素重新调整为最大堆,依次类推,最终得到排序的序列。或者说,堆排序将所有的待排序数据分为两部分,无序区和有序区。无序区也就是前面的最大堆数据,有序区是每次将堆顶元素放到最后排列而成的序列。每一次堆排序过程都是有序区元素个数增加,无序区元素个数减少的过程。当无序区元素个数为1时,堆排序就完成了。
  • 实现代码:
  •         static void HeapSort(int[] array)        {            //序列的长度            int length = array.Length;            //先建立一个堆            BuildHeap(array,length);            // 取堆顶和最后面的元素交换,剔除堆顶,剩余元素继续成堆,直到剔除所有元素            while (length > 0)            {                int temp = array[0];                array[0] = array[length - 1];                array[length - 1] = temp;                length--;  //排除堆顶出去                AdjustHeap(array,length, 0);            }        }        static void BuildHeap(int[] array, int length)        {            int index = length / 2 - 1;            while (index>=0)            {                AdjustHeap(array, length, index);                index--;            }        }        static void AdjustHeap(int[] array, int length, int index)        {            int left  = index * 2 + 1;             //左孩子坐标            int right = index * 2 + 2;         //右孩子坐标            int largest = index;                 //父节点坐标            while (left < length || right < length)            {                if (left < length && array[largest] < array[left])                {                    largest = left;                 //记录当前较大值的坐标                }                if (right < length && array[largest] < array[right])                {                    largest = right;               //记录当前较大值的坐标                }                if (largest != index)                {                    //交换数据                    int temp = array[index];                    array[index] = array[largest];                    array[largest] = temp;                    index = largest;                        left  = index * 2 + 1 ;                    right = index * 2 + 2 ;                }                else                {                    break;                }            }        }
  • 时间复杂度:在用数组的实现方式中,每次都把新加入的数值放入数组的末尾。如果这个数比它现在所在位置的父节点的数值小,那么就要把他俩交换。如果在新的位置上还是比它的父节点小,就递归地继续这个过程。这个过程和二叉树的高度成正比,所以是logn。如果把所有n个数都插入进来,那么最坏情况的时间就是O(n*logn)
  • 稳定性:堆排序是不稳定的:比如:3 27 36 27,如果堆顶3先输出,则,第三层的27(最后一个27)跑到堆顶,然后堆稳定,继续输出堆顶,是刚才那个27,这样说明后面的27先于第二个位置的27输出,不稳定.


6.总结

排序法

最差时间分析平均时间复杂度稳定度空间复杂度冒泡排序O(n2)O(n2)稳定O(1)快速排序O(n2)O(n*log2n)不稳定O(log2n)~O(n)选择排序O(n2)O(n2)稳定O(1)

插入排序

O(n2)O(n2)稳定O(1)堆排序O(n*log2n)O(n*log2n)不稳定O(1)希尔排序————不稳定O(1)排序算法.zip  


0 0