本篇文章主要讲述3种基本排序算法以及具体代码如何实现的,分别为冒泡排序,简单选择排序和直接插入排序,排序方式采用从小到大。根据在排序过程中待排序的记录是否全部被放置在内存中,排序分为:内排序和外排序。内排序是在排序整个过程中,待排序的所有记录全部被放置在内存中。外排序是由于排序的记录个数太多,不能同时放置在内存,整个排序过程需要在内存外存之间多次交换数据才能进行。排序算法的性能主要受3个方面影响:
- 时间性能:高效率的内排序算法应该是具有尽可能少的关键字比较次数和尽可能少的记录移动次数。
- 辅助空间:辅助空间是除了存放待排序所占用的存储空间之外,执行算法所需要的其他的存储空间。
- 算法的复杂性:指的是算法本身的复杂度,而不是指算法的时间复杂度。显然算法过于复杂也会影响排序的性能。
排序算法里面的常用数值交换操作:加减法、置换法、位运算法。其中,位运算发法效率最高且稳定。
(1)加减法实现方式
//加减法方式 注意:避免两数相加,大小越界
private void Swap(int[] nums, int i, int j)
{
nums[i] += nums[j];
nums[j] = nums[i] - nums[j];
nums[i] = nums[i] - nums[j];
}
(2)置换法
//数值置换方式
private void Swap(int[] nums, int i, int j)
{
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
(3)位运算法
//位运算交换方式 可以避免两数相加导致越界
private void Swap(int[] nums, int i, int j)
{
nums[i] ^= nums[j];
nums[j] ^= nums[i];
nums[i] ^= nums[j];
}
一、冒泡排序
主要思想:两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止。
下面主要实现了3中冒泡方式,层层优化,分为3个等级:初级、中级、高级。其中,优化最好的算高级,减少了比较次数。但时间复杂度仍为O(n^2)。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace 排序算法
{
//冒泡排序
class BubbleSortTest
{
private static BubbleSortTest _instance = new BubbleSortTest();
public static BubbleSortTest Instance
{
get { return _instance; }
}
//加减法方式 注意:避免两数相加,大小越界
private void Swap(int[] nums, int i, int j)
{
nums[i] += nums[j];
nums[j] = nums[i] - nums[j];
nums[i] = nums[i] - nums[j];
}
//位运算交换方式 可以避免两数相加导致越界
/*private void Swap(int[] nums, int i, int j)
{
nums[i] ^= nums[j];
nums[j] ^= nums[i];
nums[i] ^= nums[j];
}*/
//数值置换方式
/*private void Swap(int[] nums, int i, int j)
{
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}*/
//初级版 从小到大进行排序
//缺陷:已排好序的数字对剩下的没数字没什么帮助。
//具体表现为:如果开始阶段,存在较小的数在上面,则经过一次循环,会使得这个较小的数下沉,使得比较次数增加。
public void BubbleSort_Simple(int[] nums)
{
for (int i = 0; i < nums.Length; i++)
{
for (int j = i + 1; j < nums.Length; j++)
{
if (nums[i] > nums[j])
{
Swap(nums,i,j);
}
}
}
}
//中级版 从小到大进行排序
//缺陷:已符合排序要求的数列,依然需要比较n*(n-1)/2次
public void BubbleSort_Middle(int[] nums)
{
for (int i = 0; i < nums.Length; i++)
{
for (int j = nums.Length - 1; j >= i+1; j--)
{
//与初级版比较的不一样 这里是从最末端开始,与倒数第二数进行比较,让小的数逐渐往上浮
if (nums[j-1] > nums[j])
{
Swap(nums,j-1,j);
}
}
}
}
//高级版 从小到大进行排序
//优化了中级版的 比较次数问题
//此时,如果数列为有序的,则比较次数为n-1次
public void BubbleSort_High(int[] nums)
{
bool flag = true;
for (int i = 0; i < nums.Length && flag; i++)
{
flag = false;
for (int j = nums.Length - 1; j >= i + 1; j--)
{
if (nums[j - 1] > nums[j])
{
Swap(nums, j - 1,j);
flag = true;
}
}
}
}
}
}
冒泡小结:最后的改进代码高级版,最理想情况时间复杂度为O(n),比较n-1次。最差的情况,逆序时,需要比较n*(n-1)/2次,时间复杂度为O(n^2)。
二、简单选择排序
主要思想:通过n-i次关键字间的比较,从n-i+1个记录中选出关键字最小的记录,并和第i(1≤i≤n)个记录交换。换句话说,从第i+1个开始到序列结尾,挑选出最小的数,再与初始化的位置进行交换数据。
主要特点:简单选择排序的过程来看,最大的特点就是交换移动数据次数相当少。最好的时候,交换0次,最差的时候,交换n-1次。但是,无论最好还是最差的情况,比较次数均为n*(n-1)/2次。基于最终的排序时间是比较与交换次数的总和,因此,总的时间复杂度依然是O(n^2).
代码实现如下:
public void SelectSort(int[] nums)
{
int min;
for (int i = 0; i < nums.Length; i++)
{
min = i; //寻找当前剩余待排序列最小的元素
for (int j = i + 1; j < nums.Length; j++)
{
if (nums[min] > nums[j])
{
min = j;
}
}
//找到之后,才与初始位置上的元素进行交换
if (min != i)
{
Swap(nums,i,min);
}
}
}
三、直接插入排序
主要思想:将一个记录插入到已经排好序的有序表中,从而得到一个新的、记录数增1的有序表。换句话说,先确定待插入元素,再通过内层循环找到待插入的位置即可。
主要特点:当待排的序列为有序的情况,则比较次数为n-1次,由于不需要移动,此时时间复杂度为O(n)。当待排的序列为逆序的情况,则需要比较(2+n)(n-1)/2次,而记录的移动次数也达到最大值(n+4)(n-1)/2次。如果排序记录是随机的,那么根据概率相同的原则,平均比较和移动次数约为n^2/4次,因此该算法的时间复杂度为O(n^2)。
代码实现如下:
public void StragihtInsertionSort(int[] nums)
{
int temp,j; //用来临时存放待插入元素
for (int i = 1; i < nums.Length; i++)
{
//nums[i] 为待插入元素
if (nums[i] < nums[i -1])
{
temp = nums[i];
//寻找待插入位置 ,j即为待插入位置
for (j = i - 1; j >= 0 &&nums[j]>temp; j--)
{
nums[j+1] = nums[j]; //将从j位置的元素开始往后移
}
nums[j + 1] = temp;
}
}
}
总结:虽然三者都是O(n^2)时间复杂度,直接插入排序法比冒泡和简单选择排序的性能要好一些。PS:如果疑问,欢迎留言或私信我!如果文中有不当的地方,也希望大神多多指教!!谢谢!