超级具体解读基本排序算法(不看懊悔,带排序演示动画)

排序与我们日常生活中息息相关。比方。我们要从电话簿中找到某个联系人首先会依照姓氏排序、买火车票会依照出发时间或者时长排序、买东西会依照销量或者好评度排序、查找文件会依照改动时间排序等等。在计算机程序设计中,排序和查找也是最主要的算法,非常多其它的算法都是以排序算法为基础,在一般的数据处理或分析中。通常第一步就是进行排序,比方说二分查找。首先要对数据进行排序。在Donald
Knuth
的计算机程序设计的艺术这四卷书中。有一卷是专门介绍排序和查找的。

排序的算法有非常多。在维基百科上有这么一个分类,另外大家有兴趣也能够直接上维基百科上看相关算法。本文也參考了上面的内容。

首先来看比較简单的选择排序(Selection sort),插入排序(Insertion sort),然后在分析插入排序的特征和缺点的基础上。介绍在插入排序基础上改进的希尔排序(Shell sort)。

一 选择排序

原理

选择排序非常easy,他的过程例如以下:

  1. 从左至右遍历,找到最小(大)的元素。然后与第一个元素交换。
  2. 从剩余未排序元素中继续寻找最小(大)元素。然后与第二个元素进行交换。
  3. 以此类推,直到全部元素均排序完成。

之所以称之为选择排序,是由于每一次遍历未排序的序列我们总是从中选择出最小的元素。以下是选择排序的动画演示:

实现:

算法实现起来也非常easy。我们新建一个Sort泛型类,让该类型必须实现IComparable接口,然后我们定义SelectionSort方法,方法传入T数组。代码例如以下:

/// <summary>
/// 排序算法泛型类,要求类型实现IComparable接口
/// </summary>
/// <typeparam name="T"></typeparam>
public class Sort<T> where T : IComparable<T>
{
    /// <summary>
    /// 选择排序
    /// </summary>
    /// <param name="array"></param>
    public static void SelectionSort(T[] array)
    {
        int n = array.Length;

        for (int i = 0; i < n; i++)
        {
            int min = i;
            //从第i+1个元素開始,找最小值
            for (int j = i + 1; j < n; j++)
            {
                if (array[min].CompareTo(array[j]) > 0)
                    min = j;
            }
            //找到之后和第i个元素交换
            Swap(array, i, min);
        }
    }

    /// <summary>
    /// 元素交换
    /// </summary>
    /// <param name="array"></param>
    /// <param name="i"></param>
    /// <param name="min"></param>
    private static void Swap(T[] array, int i, int min)
    {
        T temp = array[i];
        array[i] = array[min];
        array[min] = temp;
    }
}

下图分析了选择排序中每一次排序的过程,您能够对比图中右边的柱状图来看。

測试例如以下:

static void Main(string[] args)
{
    Int32[] array = new Int32[] { 1, 3, 1, 4, 2, 4, 2, 3, 2, 4, 7, 6, 6, 7, 5, 5, 7, 7 };
    Console.WriteLine("Before SelectionSort:");
    PrintArray(array);
    Sort<Int32>.SelectionSort(array);
    Console.WriteLine("After SelectionSort:");
    PrintArray(array);
    Console.ReadKey();
}

输出结果:

分析:

选择排序的在各种初始条件下的排序效果例如以下:

  1. 选择排序须要花费 (N – 1) + (N – 2) + ... + 1 + 0 = N(N- 1) / 2 ~ N2/2次比較 和N-1次交换操作。

  2. 对初始数据不敏感。无论初始的数据有没有排好序,都须要经历N2/2次比較,这对于一些原本排好序。或者近似排好序的序列来说并不具有优势。

    在最好的情况下,即全部的排好序,须要0次交换,最差的情况。倒序。须要N-1次交换。

  3. 数据交换的次数较少。假设某个元素位于正确的终于位置上。则它不会被移动。在最差情况下也仅仅须要进行N-1次数据交换,在全部的全然依靠交换去移动元素的排序方法中。选择排序属于比較好的一种。

二 插入排序

原理

插入排序也是一种比較直观的排序方式。能够以我们寻常打扑克牌为例来说明,如果我们那在手上的牌都是排好序的,那么插入排序能够理解为我们每一次将摸到的牌。和手中的牌从左到右依次进行对照,如果找到合适的位置则直接插入。

详细的步骤为:

  1. 从第一个元素開始,该元素能够觉得已经被排序
  2. 取出下一个元素。在已经排序的元素序列中从后向前扫描
  3. 假设该元素小于前面的元素(已排序),则依次与前面元素进行比較假设小于则交换。直到找到大于该元素的就则停止;
  4. 假设该元素大于前面的元素(已排序),则反复步骤2
  5. 反复步骤2~4 直到全部元素都排好序 。

以下是插入排序的动画演示:

实现:

在Sort泛型方法中,我们加入例如以下方法,以下的方法和上面的定义一样

/// <summary>
/// 插入排序
/// </summary>
/// <param name="array"></param>
public static void InsertionSort(T[] array)
{
    int n = array.Length;
    //从第二个元素開始
    for (int i = 1; i < n; i++)
    {
        //从第i个元素開始,一次和前面已经排好序的i-1个元素比較,假设小于。则交换
        for (int j = i; j > 0; j--)
        {
            if (array[j].CompareTo(array[j - 1]) < 0)
            {
                Swap(array, j, j - 1);
            }
            else//假设大于。则不用继续往前比較了,由于前面的元素已经排好序,比較大的大就是教大的了。
                break;
        }
    }
}

測试例如以下:

Int32[] array1 = new Int32[] { 1, 3, 1, 4, 2, 4, 2, 3, 2, 4, 7, 6, 6, 7, 5, 5, 7, 7 };
Console.WriteLine("Before InsertionSort:");
PrintArray(array1);
Sort<Int32>.InsertionSort(array1);
Console.WriteLine("After InsertionSort:");
PrintArray(array1);
Console.ReadKey();

输出结果:

分析:

插入排序的在各种初始条件下的排序效果例如以下:

1. 插入排序平均须要N2/4次比較和N2/4 次交换。在最坏的情况下须要N2/2 次比較和交换;在最好的情况下仅仅须要N-1次比較和0次交换。

先考虑最坏情况,那就是全部的元素逆序排列,那么第i个元素须要与前面的i-1个元素进行i-1次比較和交换。全部的加起来大概等于N(N- 1) / 2 ~ N2 / 2。在数组随机排列的情况下,仅仅须要和前面一半的元素进行比較和交换,所以平均须要N2/4次比較和N2/4 次交换。

在最好的情况下,全部元素都排好序,仅仅须要从第二个元素開始都和前面的元素比較一次就可以,不须要交换。所以为N-1次比較和0次交换。

2. 插入排序中,元素交换的次数等于序列中逆序元素的对数。元素比較的次数最少为元素逆序元素的对数,最多为元素逆序的对数 加上数组的个数减1。

3.整体来说。插入排序对于部分有序序列以及元素个数比較小的序列是一种比較有效的方式。

如上图中,序列AEELMOTRXPS。中逆序的对数为T-R,T-P。T-S,R-P。X-S 6对。典型的部分有序队列的特征有:

  • 数组中每一个元素离终于排好序后的位置不太远
  • 小的未排序的数组加入到大的已排好序的数组后面
  • 数组中仅仅有个别元素未排好序

对于部分有序数组,插入排序是比較有效的。当数组中逆元素的对数越低,插入排序要比其它排序方法要高效的多。

选择排序和插入排序的比較

上图展示了插入排序和选择排序的动画效果。图中灰色的柱子是不用动的,黑色的是须要參与到比較中的,红色的是參与交换的。

图中能够看出:

插入排序不会动右边的元素。选择排序不会动左边的元素;因为插入排序涉及到的未触及的元素要比插入的元素要少,涉及到的比較操作平均要比选择排序少一半。

三 希尔排序(Shell Sort)

原理:

希尔排序也称之为递减增量排序。他是对插入排序的改进。在第二部插入排序中。我们知道。插入排序对于近似已排好序的序列来说。效率非常高,能够达到线性排序的效率。可是插入排序效率也是比較低的,他一次仅仅能将数据向前移一位。比方假设一个长度为N的序列,最小的元素假设恰巧在末尾,那么使用插入排序仍需一步一步的向前移动和比較,要N-1次比較和交换。

希尔排序通过将待比較的元素划分为几个区域来提升插入排序的效率。这样能够让元素能够一次性的朝终于位置迈进一大步,然后算法再取越来越小的步长进行排序。最后一步就是步长为1的普通的插入排序的,可是这个时候,整个序列已经是近似排好序的,所以效率高。

例如以下图,我们对以下数组进行排序的时候,首先以4为步长,这是元素分为了LMPT,EHSS。ELOX,AELR几个序列,我们对这几个独立的序列进行插入排序。排序完毕之后。我们减小步长继续排序。最后直到步长为1,步长为1即为一般的插入排序,他保证了元素一定会被排序。

希尔排序的增量递减算法能够任意指定,能够以N/2递减,仅仅要保证最后的步长为1就可以。

实现:

/// <summary>
/// 希尔排序
/// </summary>
/// <param name="array"></param>
public static void ShellSort(T[] array)
{
    int n = array.Length;
    int h = 1;
    //初始最大步长
    while (h < n / 3) h = h * 3 + 1;
    while (h >= 1)
    {
        //从第二个元素開始
        for (int i = 1; i < n; i++)
        {
            //从第i个元素開始,依次次和前面已经排好序的i-h个元素比較,假设小于,则交换
            for (int j = i; j >= h; j = j - h)
            {
                if (array[j].CompareTo(array[j - h]) < 0)
                {
                    Swap(array, j, j - h);
                }
                else//假设大于。则不用继续往前比較了,由于前面的元素已经排好序,比較大的大就是教大的了。

break;
            }
        }
        //步长除3递减
        h = h / 3;
    }
}

能够看到,希尔排序的实现是在插入排序的基础上改进的,插入排序的步长为1,每一次递减1。希尔排序的步长为我们定义的h。然后每一次和前面的-h位置上的元素进行比較。

算法中,我们首先获取小于N/3 的最大的步长,然后逐步长递减至步长为1的一般的插入排序。

以下是希尔排序在各种情况下的排序动画:

分析:

1. 希尔排序的关键在于步长递减序列的确定,不论什么递减至1步长的序列都能够,眼下已知的比較好的序列有

  • Shell‘s 序列: N/2 , N/4 , ..., 1 (反复除以2);
  • Hibbard‘s 序列: 1, 3, 7, ..., 2k - 1 ;
  • Knuth‘s 序列: 1, 4, 13, ..., (3k - 1) / 2 ;该序列是本文代码中使用的序列。

  • 已知最好的序列是 Sedgewick‘s (Knuth的学生,Algorithems的作者)的序列: 1, 5, 19, 41, 109, ....

该序列由以下两个表达式交互获得:

  • 1, 19, 109, 505, 2161,….., 9(4k – 2k) + 1, k = 0, 1, 2, 3,…
  • 5, 41, 209, 929, 3905,…..2k+2 (2k+2 – 3 ) + 1, k = 0, 1, 2, 3, …

“比較在希尔排序中是最基本的操作。而不是交换。

”用这样步长的希尔排序比插入排序和堆排序都要快,甚至在小数组中比高速排序还快,可是在涉及大量数据时希尔排序还是比高速排序慢。

2. 希尔排序的分析比較复杂。使用Hibbard’s 递减步长序列的时间复杂度为O(N3/2),平均时间复杂度大约为O(N5/4) ,详细的复杂度眼下仍存在争议。

3. 实验表明,对于中型的序列( 万)。希尔排序的时间复杂度接近最快的排序算法的时间复杂度nlogn。

四 总结

最后总结一下本文介绍的三种排序算法的最好最坏和平均时间复杂度。


名称


最好


平均


最坏


内存占用


稳定排序


插入排序


n


n2


n2


1



选择排序


n2


n2


n2


1



希尔排序


n


nlog2n

n3/2


依赖于增量递减序列眼下最好的是 nlog2n


1


希望本文对您了解以上三个主要的排序算法有所帮助,后面将会介绍合并排序和高速排序。

时间: 2024-11-05 22:53:01

超级具体解读基本排序算法(不看懊悔,带排序演示动画)的相关文章

C语言排序算法之简单交换法排序,直接选择排序,冒泡排序

C语言排序算法之简单交换法排序,直接选择排序,冒泡排序,最近考试要用到,网上也有很多例子,我觉得还是自己写的看得懂一些. 简单交换法排序 1 /*简单交换法排序 2 根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置 3 交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动 4 不稳定 5 */ 6 #include<windows.h> 7 #include<stdio.h> 8 void main(){ 9 int i,j,arr[10

【数据结构】——排序算法——3.1、选择排序

      [数据结构]--排序算法--3.1.选择排序 一.先上维基的图: 分类 排序算法 数据结构 数组 最差时间复杂度 О(n2) 最优时间复杂度 О(n2) 平均时间复杂度 О(n2) 最差空间复杂度 О(n) total, O(1)auxiliary 二.描述: 选择算法算是最直观的一个了.每次在队列里抽取一个极大(或极小)值进行排列.每次都需要遍历未被抽取的元素队列. 三.Java程序: static void selection_sort(int[] unsorted) { for

排序算法之一--冒泡排序,选择排序,插入排序

一.排序算法定义 1.排序算法定义 排序算法是一种能将一串数据依照特定顺序进行排列的一种算法 2.六种排序算法理解方式 想象小时候老师给我们按照身高进行排队时用到的方法,脑子里面要浮现老师排身高的场面   以从矮到高进行排序为例 3.稳定性的定义 一个排序算法是稳定的,当有两个相等键值的纪录R和S,且在原本的列表中R出现在S之前,在排序过的列表中R也将会是在S之前. 二.三种基本排序算法 1.冒泡排序:"移" 把最高的移到最右边 第一次循环,找到最高的那个人放到最右边       方法

常见排序算法导读(3)[简单选择排序]

这一节将介绍简单选择排序(Simple Selection Sort). 在介绍简单排序算法之前,先给出排序的确切定义,并简单介绍一下排序算法的稳定性. 排序的确切定义 假设含有n个对象的序列为{R[0], R[1], ..., R[n-1]}, 其对应的关键字(key)序列为{K[0], K[1], ..., K[n-1]}. 所谓排序, 就是确定0, 1, ..., n-1的一种排列p[0], p[1], ..., p[n-1], 使各个关键字满足如下的非递减(升序)或非递增(降序)关系:

排序算法篇--之简单选择排序

简单选择排序,就是执行n-i次比较,然后从n-i+1个数据中选择最小的值,如果最小值不是第i(1=<i<=n)个,则和第i个交换. 1 <?php 2 $arr = array(9,5,4,8,7,6,0,3,2,1); 3 4 /** 5 * 返回经过简单选择排序算法排序后的数组 6 * @param $array array 要进行排序的数组 7 * return array 进过排序后的数组 8 */ 9 function SelectSort($array){ 10 11 for

排序算法(二)——选择排序及改进

选择排序 基本思想 冒泡排序中有一个缺点,比如,我们比较第一个数a1与第二个数a2的时候,只要a1比a2大就会交换位置,但是我们并不能确定a2是最小的元素,假如后面还有比它更小的,该元素还会与a2再次进行交换,而且这种交换有可能发生多次才能确定a2的最终位置. 选择排序可以避免这种耗费时间的交换操作,从第一个元素开始,扫描整个待排数组,找到最小的元素放之后再与第一个元素交换位置,然后再从第二个元素开始,继续寻找最小的元素与第二个元素交换位置,依次类推. java实现 //选择排序 public

排序算法大集锦_各种排序算法性能比较

对10000个1-10000的随机数进行排序,并显示出运行时间 数组是用以前用VC++&MatLab生成的,比较长...哈哈,感受一下计算机的速度! #include <stdio.h> #include <string.h> #include <stdlib.h> #include <limits.h> #include <malloc.h> #include <time.h> int a[10000]={ 5282,330

排序算法&lt;No.2&gt;【桶排序】

算法,是永恒的技能,今天继续算法篇,将研究桶排序. 算法思想: 桶排序,其思想非常简单易懂,就是是将一个数据表分割成许多小数据集,每个数据集对应于一个新的集合(也就是所谓的桶bucket),然后每个bucket各自排序,或用不同的排序算法,或者递归的使用bucket sort算法,往往采用快速排序.是一个典型的divide-and-conquer分而治之的策略. 其中核心思想在于如何将原始待排序的数据划分到不同的桶中,也就是数据映射过程f(x)的定义,这个f(x)关乎桶数据的平衡性(各个桶内的数

【数据结构】常用排序算法(包括:选择排序,堆排序,冒泡排序,选择排序,快速排序,归并排序)

直接插入排序: 在序列中,假设升序排序 1)从0处开始. 1)若走到begin =3处,将begin处元素保存给tmp,比较tmp处的元素与begin--处元素大小关系,若begin处<begin-1处,将begin-1处元素移动到begin:若大于,则不变化.再用tmp去和begin--处的元素用同样的方法去作比较,直至begin此时减少到数组起始坐标0之前结束. 3)以此类推,依次走完序列. 时间复杂度:O() 代码如下: //Sequence in ascending order  voi

基础排序算法(冒泡排序,选择排序,插入排序)

最近经常调用api中的排序算法,很少自己写了,有时候也只写写快速排序这些比较快的排序,然而刚开始学排序时用的一些基本的排序算法却有点忘了 正好今天Java老师让我们每个人写个选择排序热热手,趁这个机会再来复习下一些基本的排序好了. 一.冒泡排序(稳定排序) 学编程接触到的第一个排序算法,基本思路就是,给定一个无序数组a0.a1.a2.a3....an; 通过从左到右相邻的元素两两比较,把最大或者最下的数依次放到数组的右边,最后得到有序的序列 public static void maoPao(i