数据结构与算法之美-排序(上)
排序方法
冒泡排序、插入排序、选择排序、快速排序、归并排序、计数排序、基数排序、桶排序。
复杂度归类
冒泡排序、插入排序、选择排序 O(n^2)
快速排序、归并排序 O(nlogn)
计数排序、基数排序、桶排序 O(n)
算法的执行效率
1. 最好、最坏、平均情况时间复杂度。
2. 时间复杂度的系数、常数和低阶。
3. 比较次数,交换(或移动)次数。
排序算法的稳定性
稳定性概念
如果待排序的序列中存在值相等的元素,经过排序之后,相等元素之间原有的先后顺序不变。
稳定性重要性
可针对对象的多种属性进行有优先级的排序。
排序算法的内存损耗
原地排序算法:特指空间复杂度是O(1)的排序算法。
冒泡排序
冒泡排序只会操作相邻的两个数据。每次冒泡操作都会对相邻的两个元素进行比较,看是否满足大小关系要求,如果不满足就让它俩互换。
稳定性
冒泡排序是稳定的排序算法。
空间复杂度
冒泡排序是原地排序算法。
时间复杂度
最好情况:O(n)。
最坏情况:O(n^2)。
平均情况:平均时间复杂度为O(n^2)。
C#代码实现
public void BubbleSort(int[] data,int n) { if (n <= 1) return;//长度小于1的数组直接返回 for(int j = 0; j < n; j++)//遍历数组 { Boolean flag = false;//设标志位 for (int i = 0; i < n-j-1; i++)//遍历数组未被排序的部分 { if (data[i] > data[i + 1])//比较元素大小 { int temp = data[i];//交换数据 data[i] = data[i + 1]; data[i + 1] = temp; flag = true; } } if (!flag) return;//如果一次冒泡无交换说明全部有序,返回 } }
插入排序
插入排序将数组数据分成已排序区间和未排序区间。初始已排序区间只有一个元素,即数组第一个元素。
在未排序区间取出一个元素插入到已排序区间的合适位置,直到未排序区间为空。
稳定性
插入排序是稳定的排序算法。
空间复杂度
插入排序是原地排序算法。
时间复杂度
1. 最好情况:O(n)。
2. 最坏情况:O(n^2)。
3. 平均情况:O(n^2)。
C#代码实现
public void InsertionSort(int[] data,int n) { if (n <= 1) return; for(int i = 1; i < n; i++)//遍历数组 { int value = data[i];//记录要进行排序的第i个数组成员 int j = i - 1;//从第i个数组成员前的成员开始遍历到头 for (; j >= 0; --j) { if (data[j] > value)//如果其比第i个数组成员大,将其前移 data[j + 1] = data[j]; else//否则就结束,因为前面的数据必然是有序的 break; } data[j + 1] = value;//在前移结束后停下的位置前插入记录的数据 } }
选择排序
选择排序将数组分成已排序区间和未排序区间。初始已排序区间为空。
每次从未排序区间中选出最小的元素插入已排序区间的末尾,直到未排序区间为空。
稳定性
选择排序不是稳定的排序算法。
空间复杂度
选择排序是原地排序算法。
时间复杂度
都是O(n^2))
C#代码实现
public static void SelectionSort(int[] data,int n) { if (n <= 1) return; for(int i = 0; i < n; i++)//遍历数组 { int temp = data[i];//记录要排序的数组成员 int pos = i;//记录最终的交换位置 for(int j = i; j < n; j++)//遍历数组未交换的部分,找到最小的成员并记录其位置 { if (data[j]<data[i])//如果未交换的部分比第i个成员小 { data[i] = data[j];//将其提到前面 pos = j;//记录交换的位置 } } data[pos] = temp;//互换数据 } }
思考
冒泡排序和插入排序的时间复杂度相同都是O(n^2),为什么插入排序比冒泡排序更受欢迎?
因为冒泡排序的交换操作需要执行三次操作。
如果数据存储在链表中,三种排序方法的时间复杂会变成怎样?
假定只能改变节点位置
冒泡排序,比较次数不变,因为指针,交换数据更加复杂。
插入排序,比较次数不变,但可以直接插入数据,不需要一个个地后移数据。
选择排序,比较次数不变,因为指针,交换数据更加复杂。