一、冒泡排序
冒泡排序算法得名是由于数值“像气泡一样”从一端浮动到另一端。具体思路为:多次遍历整个列,比较相邻的值并交换,将值大的元素放在后面。单看这句思路可能并不能理解冒泡排序究竟是如何实现的,那就举例:
排序数组:[5,4,3,2,1]
为了方便理解,这里元素就少点,5个就好。现在开始一步步还原过程,只需要记住一句话:多次遍历,每次遍历的目的就是将最大的数字交换到最后去(像气泡一样浮到最上层),然后下一次遍历则去掉这个最大数再重复上一次遍历的操作。
首先先尝试通过交换提第一个最大数到最后,具体过程为:
4,5,3,2,1 //比较第1、2个数字,5>4 所以交换位置
4,3,5,2,1 //比较第2、3个数字,5>3 所以交换位置
4,3,2,5,1 //比较第3、4个数字,5>2 所以交换位置
4,3,2,1,5 //比较第4、5个数字,5>1 所以交换位置
这样一来,就提了一个最大数字到了数组的最后,因为本身要排升序,那最大数已经在最后了我们就可以不用管它了,只需要在接下来的4个数字中找出第二大的就行了。
那上面一个操作称为趟的话,我们重复这个操作,可以计算出n个元素的数组只需要n-1趟就完成了排序,具体代码如下:
int[] arr = new int[]{10,35,77,95,86}
public void BubbleSort()
{
int temp;
for (int outer = arr.Length-1; 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;
}
}
}
}
上述代码,外部循环控制趟数,内部循环负责两两比较交换。
分析:
Min:最好的情况就是当排升序时,数组正好是升序,所以比较次数为n-1,实际交换的次数为0,时间复杂度为O(n)
Max:最坏的情况就是刚好逆序,这时候我们会执行n -1趟,并且每一趟比较的次数为n-i,而每一次交换都要执行3次移动操作(if语句里的3句代码),所以比较次数为 n(n-1)/2 ,移动次数为 3n(n-1)/2 。时间复杂度为 O(n^2)
二、选择排序
选择排序跟冒泡排序有些相似,理解上比冒泡排序还要简单。
思路:从数组起始处开始,把第一个元素与其他元素做比较,将最小的元素放在第0个位置上。接着去掉第一个元素,从第二个元素开始再次执行这个操作。
实现上使用了2层循环:
public void SelectionSort()
{
int min, temp;
for (int outer = 0; outer <= arr.Length-1; 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;
}
}
分析:
Min:最好的情况是已经顺序,这样交换操作是0次,比较操作是 n-1次,,时间复杂度为O(n)
Max:最坏的情况是交换n-1次,逆序的话不是最坏情况只用交换n/2次,时间复杂度为O(n)
赋值操作的次数是3(n-1) 时间复杂度O(n)
总的比较次数N=(n-1)+(n-2)+...+1=n*(n-1)/2 时间复杂度O(n^2)
由于交换所需的时间比比较所需的时间多,所以n较小时选择排序比冒泡快。但是冒泡排序是稳定排序算法,而选择排序是不稳定的,假如一组数 5 8 5 2 9 ,选择排序第一次会将5和2对换位置,这时候原序列中两个5的位置被交换了,所以它是不稳定的;而冒泡排序如果遇到相等的元素是不会交换的。
三、插入排序
插入排序的代码实现虽然没有冒泡排序和选择排序那么简单粗暴,但它的原理应该是最容易理解的了,因为只要打过扑克牌的人都应该能够秒懂。插入排序是一种最简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
先贴代码:
public void InsertionSort()
{
int inner, temp;
for (int outer = 1; outer <= arr.Length-1; outer++)
{
temp = arr[outer];
inner = outer;
while (inner > 0 && arr[inner - 1] >= temp)
{
arr[inner] = arr[inner - 1];
inner -= 1;
}
arr[inner] = temp;
}
}
这里用了两层循环,外层循环会逐个遍历数组元素,而内层循环则会把外层循环所选择的元素与该元素在数组内的下一个元素进行比较。举个例子:[6,5,4,3,2]
首先执行完第一次外循环,数组变成 5 6 4 3 2 ,这时候构建了第一个子有序序列[5,6]
执行完第二次外循环,数组变成 4 5 6 3 2,这时候子有序序列又扩大了一个元素成了 [4,5,6]
......
直到执行完所有外循环,子有序序列的元素和原数组大小一样了,排序就完成了。