排序算法-冒泡、插入、选择排序
分析排序算法从下列3个方面进行:
- 排序算法执行效率 :时间复杂度
(1)最好情况下时间复杂度
(2)最坏情况下时间复杂度
(3)平均情况时间复杂度 - 排序算法内存消耗 :空间复杂度,其中原地排序特指空间复杂度为O(1)的排序算法
- 排序算法稳定性:
(1)稳定的排序算法:待排序的序列中存在值相等的元素,经过排序后,相等元素之间前后顺序不变。
(2)不稳定的算法排序:待排序的序列中存在值相等的元素,经过排序后,相等元素之间的顺序改变。
冒泡排序
冒泡排序算法每次只会操作相邻两个数据,一次冒泡至少会让一个元素移动到对应的位置,所以重复n次就能完成n个数据的排序工作。
// 冒泡排序
static void Main(string[] args)
{
int[] arrbubblesort = new int[]{ 4,5,6,3,2,1};
BubbleSort(arrbubblesort, arrbubblesort.Length);
Console.WriteLine("Hello World!");
}
public static void BubbleSort(int[] a,int n)
{
if (n <= 1) return;
for(int i = 0; i < n; i++)
{
for(int j = 0; j < n - i - 1; j++)
{
if (a[j] > a[j + 1])
{
int temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
}
}
Console.WriteLine($"第{i + 1}次冒泡排序结果:{a[0]},{a[1]},{a[2]},{a[3]},{a[4]},{a[5]}\n");
}
}
需要对上面代码进行优化,当前代码表示不管中间是有序,都要执行n次冒泡。其实,只要在冒泡排序中检测是否有数据移动操作即可,当冒泡排序中没有进行数据移动就可以判定该序列已经是有序序列了。
public static void BubbleSort(int[] a,int n)
{
if (n <= 1) return;
for(int i = 0; i < n; i++)
{
bool isswapped = false;
for(int j = 0; j < n - i - 1; j++)
{
if (a[j] > a[j + 1])
{
int temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
isswapped = true;
}
}
Console.WriteLine($"第{i + 1}次冒泡排序结果:{a[0]},{a[1]},{a[2]},{a[3]},{a[4]},{a[5]}\n");
if (!isswapped) return;
}
}
分析优化后的冒泡排序算法:
- 冒泡排序是否时原地排序算法:是
- 冒泡排序是否是稳定的排序算法:是(if(a[j]>a[j+1])时才进行数据移动)
- 冒泡排序的时间复杂度:
最好情况时间复杂度:序列为1,2,3,4,5,6情况时时间复杂度为O(n);
最坏情况时间复杂度:序列为6,5,4,3,2,1情况时时间复杂度为O(n^2);
平均情况时间复杂度:n个数排序共有n!种情况。每种情况的移动次数不同,推导出结合概率学的数学公式比较困难。引入一种新的分析方法:有序度和逆序度。
有序度:序列中有序对的个数,当类似1,2,3…n的序列,有序度可以用公式n*(n-1)/2表示,此时也被称为满有序度。
逆序度 = 满有序度 - 有序度,逆序度也表示元素需要移动的次数。
此时分析冒泡排序平均时间复杂度:
最好情况下逆序度为0,最坏情况下逆序度为n*(n-1)/2,可以取中间值n*(n-1)/4,也就是说平均情况下需要n*(n-1)/4次交换次数,复杂度上限及最坏情况为O(n*n),所以平均时间复杂度为O(n*n)。
插入排序
插入排序将数组中的数据分为两个区间:已排序区间和未排序区间。初始已排序区间只有数组的第一个元素。插入排序需要比较目标元素和已排序区间内的各个元素,找到合适位置时需要移动元素。
// 插入排序
static void Main(string[] args)
{
int[] arrbubblesort = new int[]{ 4,5,6,3,2,1};
InsertSort(arrbubblesort, arrbubblesort.Length);
Console.WriteLine("Hello World!");
}
public static void InsertSort(int[] a,int n)
{
if (n <= 1) return;
for(int i = 1; i < n; i++)
{
int value = a[i];
int j = i - 1;
for (; j >= 0; j--)
{
if (a[j] > value)
{
a[j + 1] = a[j];
}
else
{
break;
}
}
a[j + 1] = value;
Console.WriteLine($"第{i}次插入排序:{a[0]},{a[1]},{a[2]},{a[3]},{a[4]},{a[5]}\n");
}
}
分析插入排序算法:
- 插入排序是否时原地排序算法:是
- 插入排序是否是稳定的排序算法:是(if (a[j] > value)时才移动元素)
- 插入排序的时间复杂度:
最好情况时间复杂度:1,2,3,4,5,6需要从头到尾遍历一遍数组O(n)
最坏情况时间复杂度:6,5,4,3,2,1从头到尾遍历数组并且需要从尾到头遍历已排序区间O(n*n)
平均时间复杂度:
有序数组a[n]中插入一个元素的时间复杂度:一个数插入的位置有n+1种可能,概率为1/(n+1),插入第一元素需要移动n个元素,插入最后一个需要移动0个元素。由此平均时间复杂度:n/(n+1) + (n-1)/(n+1)+…+1/(n+1) = O(n)。插入排序是遍历n个数据,每次遍历都执行一次有序数组的插入操作,所以插入操作平均时间复杂度为O(n*n)
选择排序
选择排序类似插入排序,但是插入排序是顺序遍历未排序区间序列,插入到已排序区间相应位置,选择排序是找到未排序区间中最小的元素,插入到已排序区间的末尾。
// 选择排序
static void Main(string[] args)
{
int[] arrbubblesort = new int[]{ 4,5,6,3,2,1};
SelectSort(arrbubblesort, arrbubblesort.Length);
Console.WriteLine("Hello World!");
}
public static void SelectSort(int[] a,int n)
{
if (n <= 1) return;
for(int i = 0; i < n; i++)
{
int min = a[i];
int index = i;
for (int j = i; j < n; j++)
{
if (min > a[j])
{
min = a[j];
index = j;
}
}
a[index] = a[i];
a[i] = min;
Console.WriteLine($"第{i + 1}次选择排序:{a[0]},{a[1]},{a[2]},{a[3]},{a[4]},{a[5]}\n");
}
}
分析选择排序算法:
- 选择排序是否时原地排序算法:是
- 选择排序是否是稳定的排序算法:否 如下图所示,第一次选择排序就将两个5的顺序颠倒了。
- 选择排序的时间复杂度:
最好情况时间复杂度:1,2,3,4,5,6 查找最小值需要遍历未排序区间,n个元素的排序需要循环n次查找最小值,即最好情况时间复杂度:O(n*n)
最坏情况时间复杂度:O(n*n)分析同上
平均时间复杂度:O(n*n)