数据结构与算法系列(2)基础排序算法

 

 

 

基础排序算法

——.NET数据结构与算法系列之二

追忆20131113

前言

在计算机中实现存储数据最普遍的两种操作就是排序和查找。这是从计算机产业初始就已经确认的了。这意味着排序和查找也是计算机科学领域最值得研究的两种操作。本书提到的许

多数据结构的主要设计目的就是为了使排序和/或查找更加简单,同时也是为了数据在结构内的存储更加有效。

本章会介绍有关数据排序和查找的基础算法。这些算法仅依赖数组作为数据结构,而且所采用的“高级”编程技术只是递归。本章还介绍了用来非正式分析不同算法之间速度与效率的方法,此方法贯穿全书。

 

1.排序算法

人们在日常生活中所接触到的绝大多数数据都是经过排序的。比如,按照字母顺序查询字典中的定义。或者按照名字的字母顺序在电话本中查询电话号码。再或者邮局会按照下列几个

步骤对邮件进行排序分发:即首先按照邮政编码,然后再按照街道名称,最后还要按照姓名。排序在数据处理中是十分基础的过程,因而值得认真学习研究。正如先前提到的那样,这里对不同排序算法的操作有非常少量的分析研究。尽管已经对一些非常古老的算法做了改进,但是仍然应该先学习几种简单的排序算法。这些简单算法就是插入排序算法、冒泡排序算法以及选择排序算法。这些算法的每一种都很容易理解和实现。对于任意情况而言这些算法不是最好的全面算法,但是对于少量数据集合或者其他特殊情况而言,它们是可用的最好算法。

 

1.1数组类测试环境

为了检验这些算法,首先需要构造一个可以实现并测试算法的测试环境。这里将构造一个类来封装数组处理的一些常规操作,即元素插入操作,元素存取访问操作,以及显示数组内容的操组。下面就是程序的代码:

 

在保留CArray 类以便开始检测排序和查找算法之前,还是先来讨论一下如何在CArray 类对象内实际存储数据的问题。为了更有效地说明不同排序算法是如何运行的,数组内数据需

要随机放置。最好的实现方法就是使用随机数生成器来给数组的每个元素进行赋值。在C#中用Random 类可以产生随机数。这种类型的对象可以产生随机数。为了实例化Random 对象,需要给这个类的构造器传递一个种子。这里把这个种子看作是随机数生成器所能产生的随机数范围的上界。

下面另外看一个用CArray 类来存储数的程序,而且采用了随机数生成器来选择存储到数组内的数据:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

 

namespace Chapter2

{

    class Program

    {

        static void Main(string[] args)

        {

            CArray nums = new CArray(10);

 

            Random rnd = new Random(100);

 

            for (int i = 0; i <10; i++)

            {

                nums.Insert(rnd.Next(0,100));

            }

 

            nums.DisplayElements();

 

            Console.ReadKey();

        }

    }

}

 

 

1. 2冒泡排序

首先要讨论的排序算法就是冒泡排序。冒泡排序是可用的最慢排序算法之一,但是它也是最容易理解和实现的排序算法之一,所以这里把它作为最先介绍的排序算法。

这种排序算法的得名是由于数值“像气泡一样”从序列的一端浮动到另一端。假设现在要把一列数按升序方式进行排序,即较大数值浮动到列的右侧,而较小数值则浮动到列的左侧。

这种效果可以通过下列操作来实现:多次遍历整个列,并且比较相邻的数值,如果左侧的数值大于右侧数值就进行交换。

        //冒泡排序

public void BubbleSort()

        {

            int temp;

            int num = 0;

 

            for (int outer = upper; outer >= 1; outer--)

            {

                num++;

                for (int inner = 0; inner <= outer - 1; inner++)

                {

                    if (arr[inner] > arr[inner + 1])

                    {

                        temp = arr[inner];

                        arr[inner] = arr[inner + 1];

                        arr[inner + 1] = temp;

                    }

                }

 

                Console.WriteLine(string.Format("\n{0}次排序", num));

                this.DisplayElements();

            }

        }

clip_image002
这段代码有几个地方需要注意。首先,交换数组元素的代码是写在主程序中的一行,而没有用子程序。如果多次调用交换子程序,就可能会降低排序的速度。既然交换代码只有短短三

行的长度,所以不把代码放在子程序内也不会影响代码的清晰度。更加需要注意的是程序中最外层的循环是从数组的末尾处开始,并且向数组的开始处移动。如果回顾上图过程就会知道,数组内最大值就在数组末尾的适当位置上。这意味着数组的索引比外层循环的值更大,而且它们已经在恰当的位置上了,因而算法不需要再访问这些数值了。内层循环从数组的第一个元素开始,并且在几乎达到数组最后位置的时候结束。内层循环会对用inner inner+1 标识的两个相邻位置的数值进行比较,并且在必要时交换它们的数值。

1.3选择排序

这种排序是从数组的起始处开始,把第一个元素与数组中其他元素进行比较。然后,将最小的元素放置在第0 个位置上,接着再从第1 个位置开始再次进行排序操作。这种操作会一直到除最后一个元素外的每一个元素都作为新循环的起始点操作过后才终止。

在选择排序算法中使用了两层循环。外层循环从数组的第一个元素移动到数组最后一个元素之前的元素,而内层循环则从数组的第二个元素移动到数组的最后一个元素,并且查找比当前外层循环所指元素更小的数值。在内循环遍历一遍之后,就会把数组内最小值赋值到数组中合适的位置上。

实现SelectionSort 算法的代码如下所示:

//选择排序

        public void SelectionSort()

        {

            int min, temp;

           

            for (int outer = 0; outer <= upper; outer++)

            {

                min = outer;

           

                for (int inner = outer + 1; inner <= upper; inner++)

                {

                    if (arr[min] > arr[inner] )

                        min = inner;

                }

               

                temp = arr[outer];

                arr[outer] = arr[min];

                arr[min] = temp;

 

                Console.WriteLine(string.Format("\n{0}次排序", outer+1));

                this.DisplayElements();

            }

        }

         clip_image004


1.4
插入排序

插入排序算法类似于人们通常按照数字顺序或者字母顺序进行排序的方法。假如我要求全班同学上交填有本人姓名、学号以及简短自我介绍的索引卡片。而学生们交回来的卡片是随机排列的。如果要把卡片按照字母排序排列,就可以构建出一张座次表了。所以,我把这些卡片带回了办公室,并且清理出了办公桌。紧接着我拿出了第一张卡片。卡片上的名字是Smith。我把它放在办公桌最左侧的位置上,然后又拿出了第二张卡片。这张是Brown。于是,我把Smith 的卡片移动到右侧,并且把Brown 的卡片放到Smith 原来的位置上。下一张卡片是Williams。不需要移动任何其他的卡片就可以把它放在最右侧的位置上。接下来的卡片是Acklin。它需要放置在队列的开始处,所以其他所有的卡片都必须向右移动一个位置以便腾出空间放Acklin。这就是插入排序算法的工作原理。

插入排序的代码如下所示,跟着的是对此算法工作原理的解释说明:

        //插入排序

        public void InsertionSort()

        {

            int inner, temp;

 

            for (int outer = 1; outer <= upper; outer++)

            {

                temp = arr[outer];

                inner = outer;

 

                while (inner > 0 && arr[inner - 1] >= temp)

                {

                    arr[inner] = arr[inner-1];

                    inner-=1;

                }

                arr[inner] = temp;

               

                Console.WriteLine(string.Format("\n{0}次排序", outer));

                this.DisplayElements();

            }

        }

 

插入排序算法有两层循环。外层循环会逐个遍历数组元素,而内层循环则会把外层循环所选择的元素与该元素在数组内的下一个元素进行比较。如果外层循环选择的元素小于内层循环选择的元素,那么数组元素都向右移以便为内层循环元素留出位置,这就像前面例子描述的那样。现在就来看看选择排序是如何处理前面实例中用来排序的数据集合的。下面是程序的输出结果:

clip_image006

 

这个输出清楚地表明插入排序不是通过交换来处理的,而是通过把较大的数组元素向右移动来为数组左侧较小元素留出空间的方式进行操作的。

 

2.基础排序算法的时间比较

上述三种排序算法在复杂度和理论上都是十分相似的,所以在互相进行比较的时候应该操作近似。这里用Timing 类来比较三种算法,根据它们对庞大数据集合进行排序时所花费的时间判定出是否有算法会与众不同。为了进行测试,这里用到基本代码和之前为了说明每种算法的工作原理而使用的代码完全一样。但是,在下面这些测试中,为了说明三种算法是如何处理较小数据集合和较大数据集合的,数组的大小是有变化的。时间测试程序要分别运行处理元素量为100100010000至更多的几种情况。下面是代码:

      //基础排序算法的时间比较

    class Test

    {

        public static void RunTest(int numItems)

        {

            Timing sortTime = new Timing();

            Random rnd = new Random(100);

            CArray theArray = new CArray(numItems);

           

            //选择排序

            for (int i = 0; i < numItems; i++)

                theArray.Insert(rnd.Next(1, int.MaxValue) * 100);

            sortTime.StartTime();

            theArray.SelectionSort();

            sortTime.StopTime();

            Console.WriteLine("Time for Selection sort: " + sortTime.Result().TotalMilliseconds);

            theArray.Clear();

           

            //冒泡排序

            for (int i = 0; i < numItems; i++)

                theArray.Insert(rnd.Next(1, int.MaxValue) * 100);

            sortTime.StartTime();

            theArray.BubbleSort();

            sortTime.StopTime();

            Console.WriteLine("Time for Bubble sort: " + sortTime.Result().TotalMilliseconds);

            theArray.Clear();

           

            //插入排序

            for (int i = 0; i < numItems; i++)

                theArray.Insert(rnd.Next(1, int.MaxValue) * 100);

            sortTime.StartTime();

            theArray.InsertionSort();

            sortTime.StopTime();

            Console.WriteLine("Time for Insertion sort: " + sortTime.Result().TotalMilliseconds);

        }

}

clip_image008

 

结果大概如上所示,实在是等不及啦,没等后两个测试出来,就把它给闭了!大家见谅!

尽管选择排序始终比其他两种算法快出许多倍,但是所有这三种排序算法的性能还是相当低的。准确地说,这些算法没有一种在对庞大数据集合进行排序时是理想选择。但是存在能高效处理庞大数据集合的排序算法。在后面的内容里将会和大家一起探讨。

 

小结

本章讨论了针对数据排序的三种算法,即选择排序、冒泡排序以及插入排序。所有这三种算法都是非常容易实现的,而且它们都可以很好地处理少量的数据集合。选择排序是三种算法中效率最高的,其次是冒泡排序和插入排序。正如本章末尾看到的那样,这三种算法没有一种是十分适合庞大数据集合的。(比如,多于万个元素的数据集合)。

 

源程序下载:DataStructAndAlgorithm.zip 

      参考书箱:<<数据结构与算法>>

posted @ 2013-11-15 15:03  coderi++  阅读(695)  评论(0编辑  收藏  举报