首页 > 代码库 > [MOOC笔记]排序专题(数据结构)
[MOOC笔记]排序专题(数据结构)
1.冒泡排序:
思路:将相邻的逆序元素交换为顺序排列,直到整个序列有序,算法如下:
/** * 冒泡排序-最初实现,时间复杂度O(n^2) * @param arr 待排序的数组 * @param lo 待排序区间的起始位置 * @param hi 待排序区间的结束位置 */ public static void bubbleSort(int[] arr, int lo, int hi) { //对序列进行规模n-1次扫描 for (int i = lo; i < hi - 1; i++) { //每次扫描都会将当前区间最大的元素移到末尾,所以每次扫描的范围都可以减少1 for (int j = lo; j < hi - i - 1; j++) { //判断相邻元素是否为逆序,是的话交换 if (arr[j] > arr[j + 1]) { swap(arr, j, j + 1); } } } }
以上算法在任何情况都会对一个规模为n的序列进行n-1次扫描,即使该序列在中途已经变成有序。所以需要一个标示位来记录每次扫描后该序列是否已经完成排序,改进算法如下:
/** * 冒泡排序-改进一:记录扫描结果,时间复杂度O(n^(3/2)) * @param arr 待排序的数组 * @param lo 待排序区间的起始位置 * @param hi 待排序区间的结束位置 */ public static void bubbleSort(int[] arr, int lo, int hi) { //对序列进行规模n-1次扫描 for (int i = lo; i < hi - 1; i++) { //设置一个标示位记录序列是否为有序状态 boolean sorted = true; //每次扫描都会将当前区间最大的元素移到末尾,所以每次扫描的范围都可以减少1 for (int j = lo; j < hi - i - 1; j++) { //判断相邻元素是否为逆序,是的话交换并将有序标示置为false if (arr[j] > arr[j + 1]) { swap(arr, j, j + 1); sorted = false; } } //当一次扫描后有序标示扔为true,则序列已经有序,停止扫描 if (sorted) { break; } } }
改进后的算法还是有缺陷,即使扫描的次数动态的减少了,但是每次扫描的范围还是固定的。于是接着改进:
/** * 冒泡排序-改进二:记录逆序范围,时间复杂度O(n) * @param arr 待排序的数组 * @param lo 待排序区间的起始位置 * @param hi 待排序区间的结束位置 */ public static void bubbleSort(int[] arr, int lo, int hi) { //在可能存在逆序的区间消失前一直扫描 while (lo < hi) { //将秩设为区间的起始位置 int last = lo; //扫描整个区间 for (int j = lo; j < hi - 1; j++) { //判断相邻元素是否为逆序,是的话交换并记录下位置 if (arr[j] > arr[j + 1]) { swap(arr, j, j + 1); last = j + 1; } } //将最后一次进行交换的位置设为区间的结束位置(之后的所有元素均为有序排列) hi = last; } }
三种冒泡排序的时间成本如图所示:
2.选择排序:
思路:每次选取区间内最大(小)的元素移动到区间前端,直到所有元素被选取完毕,算法如下:
/** * 选择排序,时间复杂度O(n^2) * @param arr 待排序的数组 * @param lo 待排序区间的起始位置 * @param hi 待排序区间的结束位置 */ public static void selectionSort(int[] arr, int lo, int hi) { //对序列进行规模n-1次扫描 for (int i = lo; i < hi - 1; i++) { int max = arr[i], index = i; //每次扫描都会记录当前区间最小的元素 for (int j = i + 1; j < hi; j++) { if (arr[j] < max) { max = arr[j]; index = j; } } //将最小元素移到区间最前面 if (index != i) { swap(arr, i, index); } } }
选择排序的主要时间成本在于选取区间内的最大元素,这个操作的时间复杂度是O(n),而有种方法可以将这个时间成本降低至O(logn),这将会在之后的堆排序中介绍。
3.归并排序
思路:将整个序列分为多个子序列,并依次将子序列排序,最后合并成有序序列,算法如下:
/** * 归并排序,时间复杂度O(nlogn) * @param arr 待排序的数组 * @param lo 待排序区间的起始位置 * @param hi 待排序区间的结束位置 */ public static void mergeSort(int[] arr, int lo, int hi) { //如果该区间只剩下一个元素,则推出递归 if (hi - lo < 2) { return; } //以中点为轴点分割两个区间 int mid = (hi + lo) >> 1; //分别递归两个区间 mergeSort(arr, lo, mid); mergeSort(arr, mid, hi); //将两个区间合并 merge(arr, lo, mid, hi); } /** * 归并排序的合并区间 * @param arr 待排序的数组 * @param lo 待合并区间的起始位置 * @param mid 区间分隔的点 * @param hi 待合并区间的结束位置 */ private static void merge(int[] arr, int lo, int mid, int hi) { //建立临时数组备份待排序区间 int[] temp = new int[hi - lo]; for (int i = lo; i < hi; i++) { temp[i - lo] = arr[i]; } //设置两个秩分别指向两个临时区间的起始位置,设置一个秩指向整个区间的起始位置 int i = 0, j = mid - lo, k = lo; //当整个区间的秩指向区间的结束位置时停止 while (k < hi) { //将两个秩指向的元素中较小者存放到待排序区间 if ((i < mid - lo) && (j >= hi - lo || temp[i] < temp[j])) { arr[k++] = temp[i++]; } if ((j < hi - lo) && (i >= mid - lo || temp[j] < temp[i])) { arr[k++] = temp[j++]; } } }
4.插入排序
思路:将每个元素取出并插入到合适的位置,使该元素与它前一个和后一个元素均为顺序排列,直到所有元素插入完毕。算法如下:
/** * 插入排序,时间复杂度O(n^2) * @param arr 待排序的数组 * @param lo 待排序区间的起始位置 * @param hi 待排序区间的结束位置 */ public static void insertionSort(int[]arr, int lo, int hi) { //扫描每一个元素 for (int i = lo + 1; i < hi; i++) { //如果该元素比它前一个元素要小 if (arr[i] < arr[i - 1]) { //记录下它的位置和值 int temp = arr[i]; int index = i; //将比它大的元素依次后移 while (index > 0 && arr[index - 1] > temp) { arr[index] = arr[index - 1]; index--; } //将它插入到合适的位置 arr[index] = temp; } } }
未完待续....
[MOOC笔记]排序专题(数据结构)
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。