第3章.基础排序算法
3.1.1 数组类测试环境
为了检验这些算法,首先需要构造一个可以实现并测试算法的测试环境。这里将构造一个类来封装数组处理的一些常规操作,即元素插入操作,元素存取访问操作,以及显示数组内容的操作。下面就是程序的代码:
namespace 第3章.基础排序算法 { class Program { static void Main(string[] args) { CArry nums = new CArry(50); for (int i = 0; i <= 49; i++) { nums.Insert(i); } nums.DisplayElements(); Console.ReadKey(); } } class CArry { private int[] arr; private int upper; private int numElements; public CArry(int size) { arr = new int[size]; upper = size - 1; numElements = 0; } public void Insert(int item) { arr[numElements] = item; numElements++; } public void DisplayElements() { for (int i = 0; i <= upper; i++) { Console.Write(arr[i] + " "); } } public void Clear() { for (int i = 0; i <= upper; i++) { arr[i] = 0; } numElements = 0; } } }
程序的输出如下所示:
在保留CArry类以便开始检测排序和查找算法之前,还是先来讨论一下如何在CArry类对象内实际存储数据的问题。为了更有效的说明不同排序算法是如何运行的,数组内数据需要随机放置。最好的实现方法就是使用随机数生成器来给数组的每个元素进行赋值。
在C#中用Random类可以产生随机数,这种类型的对象可以产生随机数。为了实例化Random对象,需要给这个类的构造器传递一个种子。这里把这个种子看作是随机数生成器所能产生的随机数范围的上界。
下面另外看一个用CArry类来存储数的程序,而且采用了随机数生成器来选择存储到数组内的数据:
namespace 第3章.基础排序算法 { class Program { static void Main(string[] args) { CArry nums = new CArry(10); Random r = new Random(100); for (int i = 0; i < 10; i++) { nums.Insert(r.Next(0, 100)); } nums.DisplayElements(); Console.ReadKey(); } } class CArry { private int[] arr; private int upper; private int numElements; public CArry(int size) { arr = new int[size]; upper = size - 1; numElements = 0; } public void Insert(int item) { arr[numElements] = item; numElements++; } public void DisplayElements() { for (int i = 0; i <= upper; i++) { Console.Write(arr[i] + " "); } } public void Clear() { for (int i = 0; i <= upper; i++) { arr[i] = 0; } numElements = 0; } } }
这段程序输出结果如下所示:
3.1.2 冒泡排序
首先要讨论的排序算法就是冒泡排序。冒泡排序是可用的最慢排序算法之一,但它也是最容易理解和实现的排序算法之一,所以这里把它作为最先介绍的排序算法。
这种排序算法的得名是由于数值“像气泡一样”从序列的一端浮动到另一端。假设现在要把一列数按升序方式进行排序,即较大数值浮动到列的右侧,而较小数值则浮动到列的左侧。这种效果可以通过下列操作来实现:多次遍历这个列,并且比较相邻的数值,如果左侧的数值大于右侧的数值就进行交换。
如图说明了冒泡排序算法的工作原理。图中的两个数字(2和72)用圆圈进行了突出表示,这两个数是上一个实例中要插入数组其中的两个数。从图上可以看出数字72是如何从数组的开头移动到数组的中部,而数字2又是如何从数组的后半部分移动到了数组的开头。
BubbleSort算法的代码如下所示:
public void BubbleSort() { int temp; for (int outer = upper; outer >= 1; outer--) { for (int inner = 0; inner <= outer - 1; inner++) { if ((int)arr[inner] > arr[inner + 1]) { temp = arr[inner]; arr[inner] = arr[inner + 1]; arr[inner + 1] = temp; } } this.DisplayElements(); } }
这段代码有几个地方需要注意。首先,交换数组元素的代码是写在主程序中的一行,而没有用子程序。如果多次调用交换子程序,就可能降低排序的速度。既然交换代码只有短短三行的长度,所以不把代码放在子程序内也不会影响代码的清晰度。
更加需要注意的是程序中最外层的循环是从数组的末尾处开始,并且向数组的开始处移动。回顾上图就会知道,数组内最大值就在数组末尾的适当位置上。这意味着数组的索引比外层循环的值更大,而且它们已经在恰当的位置上了,因而算法不需要在访问这些数值了。
内层循环从数组的第一个元素开始,并且在几乎达到数组最后位置的时候结束。内层循环会对用inner和inner+1标识的两个相邻位置的数值进行比较,并且在必要时交换它们的数值。
3.1.3 校验过程排序
对于前面提到的BubbleSort方法而言,检测数组在排序过程中如何变化的最佳位置就是在内、外层循环之间。如果为两个循环的每次重复执行插入输出显示,就可以看到数值在排序过程中如何在数组中移动的记录。例如:
namespace 第3章.基础排序算法 { class Program { static void Main(string[] args) { CArry nums = new CArry(10); Random r = new Random(100); for (int i = 0; i < 10; i++) { nums.Insert(r.Next(0, 100)); } Console.WriteLine("Before sorting: "); nums.DisplayElements(); Console.WriteLine("During sorting: "); nums.BubbleSort(); Console.WriteLine("After sorting: "); nums.DisplayElements(); Console.ReadKey(); } } class CArry { private int[] arr; private int upper; private int numElements; public CArry(int size) { arr = new int[size]; upper = size - 1; numElements = 0; } public void Insert(int item) { arr[numElements] = item; numElements++; } public void DisplayElements() { for (int i = 0; i <= upper; i++) { Console.Write(arr[i] + " "); } Console.WriteLine(""); } public void Clear() { for (int i = 0; i <= upper; i++) { arr[i] = 0; } numElements = 0; } public void BubbleSort() { int temp; for (int outer = upper; outer >= 1; outer--) { for (int inner = 0; inner <= outer - 1; inner++) { if ((int)arr[inner] > arr[inner + 1]) { temp = arr[inner]; arr[inner] = arr[inner + 1]; arr[inner + 1] = temp; } } this.DisplayElements(); } } } }
那么程序的输出如下所示:
参考图片:
参考链接:https://zhidao.baidu.com/question/514149220.html
3.1.4 选择排序
下一个要讨论的排序算法是选择排序。这种排序是从数组的起始处开始,把第一个元素与数组中其它元素进行比较。然后,将最小的元素放置在第0个位置上,接着在从第1个位置开始再次进行排序操作。这种操作会一直到除最后一个元素外的每一个元素都作为新循环的起始点操作过后才终止。
在选择排序算法中使用了两层循环。外层循环从数组的第一个元素移动到数组的最后一个元素之前的元素,而内层循环则从数组的第二个元素移动到数组的最后一个元素,并且查找比当前外层循环所指元素更小的数值。在内循环遍历一遍之后,就会把数组内最小值赋值到数组中合适的位置上。如图所示:
实现SelectionSort算法的代码如下所示:
namespace 第3章.基础排序算法 { class Program { static void Main(string[] args) { CArry nums = new CArry(10); Random r = new Random(); for (int i = 0; i < 10; i++) { nums.Insert(r.Next(0, 100)); } Console.WriteLine("Before sorting: "); nums.DisplayElements(); Console.WriteLine("During sorting: "); nums.SelectionSort(); Console.WriteLine("After sorting: "); nums.DisplayElements(); Console.ReadKey(); } } class CArry { private int[] arr; private int upper; private int numElements; public CArry(int size) { arr = new int[size]; upper = size - 1; numElements = 0; } public void Insert(int item) { arr[numElements] = item; numElements++; } public void DisplayElements() { for (int i = 0; i <= upper; i++) { Console.Write(arr[i] + " "); } Console.WriteLine(""); } public void Clear() { for (int i = 0; i <= upper; i++) { arr[i] = 0; } numElements = 0; } 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[inner] < arr[min]) { min = inner; } } temp = arr[outer]; arr[outer] = arr[min]; arr[min] = temp; this.DisplayElements(); } } } }
输出结果如下所示:
参考理解如图所示:
3.1.5 插入排序
插入排序算法类似于人们通常按照数字顺序或字母顺序进行排序的方法。假如我要求全班同学上交填有本人姓名、学号及简短自我介绍的索引卡片。而学生们交回来的卡片是随机排列的。如果要把卡片按照字母顺序排列,就可以构建出一张座次表了。
所以,我把这些卡片带回了办公室,并且清理出了办公桌。紧接着我拿出了第一张卡片。卡片上的名字是Smith。我把它放在办工作最左侧的位置上,然后又拿出了第二张卡片。这张是Brown。于是,我把Smith的卡片移动到右侧,并且把Brown的卡片放到原来Smith原来的位置上。下一张卡片是Williams。不需要移动任何其它的卡片就可以把它放在最右侧的位置上。接下来的卡片是Acklin。它需要放置在队列的开始处,所以其它所有的卡片都必须向右移动一个位置以便腾出空间放Acklin。这就是插入排序算法的工作原理。
插入排序的代码如下所示:
namespace 第3章.基础排序算法 { class Program { static void Main(string[] args) { CArry nums = new CArry(10); Random r = new Random(); for (int i = 0; i < 10; i++) { nums.Insert(r.Next(0, 100)); } Console.WriteLine("Before sorting numbers: "); nums.DisplayElements(); Console.WriteLine("Now into Insertion sort: "); nums.InsertionSort(); Console.WriteLine("After sorting numbers: "); nums.DisplayElements(); Console.ReadKey(); } } class CArry { private int[] arr; private int upper; private int numElements; public CArry(int size) { arr = new int[size]; upper = size - 1; numElements = 0; } public void Insert(int item) { arr[numElements] = item; numElements++; } public void DisplayElements() { for (int i = 0; i <= upper; i++) { Console.Write(arr[i] + " "); } Console.WriteLine(""); } public void Clear() { for (int i = 0; i <= upper; i++) { arr[i] = 0; } numElements = 0; } 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; this.DisplayElements(); } } } }
插入排序算法有两层循环。外层循环会逐个遍历数组元素,而内层循环则会把外层循环所选择的元素与该元素在数组内的下一个元素进行比较。如果外层循环选择的元素小于内层循环选择的元素,那么数组元素都向右移以便为内层循环元素留出位置,就像前面例子描述的那样。
现在就来看看选择排序是如何处理前面实例中用来排序的数据集合的,下面是程序的输出结果:
这个输出清楚的表明插入排序不是通过交换来处理的,而是通过把较大的数组元素向右移动来为数组左侧较小元素留出空间的方式进行操作的。
参考链接:
https://www.cnblogs.com/weiios/p/3933985.html
https://www.cnblogs.com/JiYF/p/8849819.html
3.2 基础排序算法的时间比较
选择排序是三种算法中效率最高的,其次是冒泡排序和插入排序。