插入类排序就是在一个有序的序列中,插入一个新的关键字。从而达到新的有序序列。插入排序通常有直接插入排序、折半插入排序和希尔排序。html
/** * 直接比较,将大元素向后移来移动数组 */ public static void InsertSort(int[] A) { for(int i = 1; i < A.length; i++) { int temp = A[i]; //temp 用于存储元素,防止后面移动数组被前一个元素覆盖 int j; for(j = i; j > 0 && temp < A[j-1]; j--) { //若是 temp 比前一个元素小,则移动数组 A[j] = A[j-1]; } A[j] = temp; //若是 temp 比前一个元素大,遍历下一个元素 } } /** * 这里是经过相似于冒泡交换的方式来找到插入元素的最佳位置。而传统的是直接比较,移动数组元素并最后找到合适的位置 */ public static void InsertSort2(int[] A) { //A[] 是给定的待排数组 for(int i = 0; i < A.length - 1; i++) { //遍历数组 for(int j = i + 1; j > 0; j--) { //在有序的序列中插入新的关键字 if(A[j] < A[j-1]) { //这里直接使用交换来移动元素 int temp = A[j]; A[j] = A[j-1]; A[j-1] = temp; } } } } /** * 时间复杂度:两个 for 循环 O(n^2) * 空间复杂度:占用一个数组大小,属于常量,因此是 O(1) */
/* * 从直接插入排序的主要流程是:1.遍历数组肯定新关键字 2.在有序序列中寻找插入关键字的位置 * 考虑到数组线性表的特性,采用二分法能够快速寻找到插入关键字的位置,提升总体排序时间 */ public static void BInsertSort(int[] A) { for(int i = 1; i < A.length; i++) { int temp = A[i]; //二分法查找 int low = 0; int high = i - 1; int mid; while(low <= high) { mid = (high + low)/2; if (A[mid] > temp) { high = mid - 1; } else { low = mid + 1; } } //向后移动插入关键字位置后的元素 for(int j = i - 1; j >= high + 1; j--) { A[j + 1] = A[j]; } //将元素插入到寻找到的位置 A[high + 1] = temp; } }
希尔排序又称缩小增量排序,其本质仍是插入排序,只不过是将待排序列按某种规则分红几个子序列,而后如同前面的插入排序通常对这些子序列进行排序。所以当增量为 1 时,希尔排序就是插入排序,因此希尔排序最重要的就是增量的选取。java
主要步骤是:git
/** * 希尔排序的实现代码仍是比较简洁的,除了增量的变化,基本上和直接插入序列没有区别 */ public static void ShellSort(int[] A) { for(int d = A.length/2; d >= 1; d = d/2) { //增量的变化,从 d = "数组长度一半"到 d = 1 for(int i = d; i < A.length; i++) { //在一个增量范围内进行遍历[d,A.length-1] if(A[i] < A[i - d]) { //若增量后的元素小于增量前的元素,进行插入排序 int temp = A[i]; int j; for(j = i - d; j >= 0 && temp < A[j-d]; j -= d) { //对该增量序列下的元素进行排序 A[j + d] = A[j]; //这里要使用i + d 的方式来移动元素,由于增量 d 可能大于数组下标 } //形成数组序列超出数组的范围 A[j + d] = temp; } } } }
排序方法 | 空间复杂度 | 最好状况 | 最坏状况 | 平均时间复杂度 |
---|---|---|---|---|
直接插入排序 | O(1) | O(n^2) | O(n^2) | O(n^2) |
折半插入排序 | O(1) | O(nlog2n) | O(n^2) | O(n^2) |
希尔排序 | O(1) | O(nlog2n) | O(nlog2n) | O(nlog2n) |
交换,指比较两个元素关键字大小,来交换两个元素在序列中的位置,最后达到整个序列有序的状态。主要有冒泡排序和快速排序算法
冒泡排序就是经过依次比较序列中两个相邻元素的值,根据须要的升降序来交换这两个元素。最终达到整个序列有序的结果。segmentfault
/** * 冒泡排序 */ public static void BubbleSort(int[] A) { for (int i = 0; i < A.length - 1; i++) { //冒泡次数,遍历数组次数,有序元素个数 for(int j = 0; j < A.length - i - 1; j++) { //对剩下无序元素进行交换排序 if(A[j] > A[j + 1]) { int temp = A[j]; A[j] = A[j + 1]; A[j + 1] = temp; } } } }
快速排序实际上也是属于交换类的排序,只是它经过屡次划分操做实现排序。这就是分治思想,把一个序列分红两个子序列它每一趟选择序列中的一个关键字做为枢轴,将序列中比枢轴小的移到前面,大的移到后边。当本趟全部子序列都被枢轴划分完毕后获得一组更短的子序列,成为下一趟划分的初始序列集。每一趟结束后都会有一个关键字达到最终位置。数组
/** * 快速排序算是在冒泡排序的基础上的递归分治交换排序 * @param A 待排数组 * @param low 数组起点 * @param high 数组终点 */ public static void QuickSort(int[] A, int low, int high) { if(low >= high) { //递归分治完成退出 return; } int left = low; //设置左遍历指针 left int right = high; //设置右遍历指针 right int pivot = A[left]; //设置枢轴 pivot, 默认是数组最左端的值 while(left < right) { //循环条件 while(left < right && A[right] >= pivot) {//若右指针所指向元素大于枢轴值,则右指针向左移动 right--; } A[left] = A[right]; //反之替换 while (left < right && A[left] <= pivot) {//若左指针所指向元素小于枢轴值,则左指针向右移动 left++; } A[right] = A[left]; //反之替换 } A[left] = pivot; //将枢轴值放在最终位置上 QuickSort(A, low, left - 1); //依此递归枢轴值左侧的元素 QuickSort(A, left + 1, high); //依此递归枢轴值右侧的元素 }
排序方法 | 空间复杂度 | 最好状况 | 最坏状况 | 平均时间复杂度 |
---|---|---|---|---|
冒泡排序 | O(1) | O(n^2) | O(n^2) | O(n^2) |
快速排序 | O(log2n) | O(nlog2n) | O(n^2) | O(nlog2n) |
选择排序就是每一趟从待排序列中选择关键字最小的元素,直到待排序列元素选择完毕。数据结构
/** * 简单选择排序 * @param A 待排数组 */ public static void SelectSort(int [] A) { for (int i = 0; i < A.length; i++) { int min = i; //遍历选择序列中的最小值下标 for (int j = i + 1; j < A.length; j++) { //遍历当前序列选择最小值 if (A[j] < A[min]) { min = j; } } if (min != i) { //选择并交换最小值 int temp = A[min]; A[min] = A[i]; A[i] = temp; } } }
堆是一种数据结构,能够把堆当作一颗彻底二叉树,并且这棵树任何一个非叶结点的值都不大于(或不小于)其左右孩子结点的值。若父结点大子结点小,则这样的堆叫作大顶堆;若父结点小子结点大,则这样的堆叫作小顶堆。函数
堆排序的过程实际上就是将堆排序的序列构形成一个堆,将堆中最大的取走,再将剩余的元素调整成堆,而后再找出最大的取走。这样重复直至取出的序列有序。ui
排序主要步骤能够分为(以大顶堆为例):指针
(1) 将待排序列构形成一个大顶堆:BuildMaxHeap()
(2) 对堆进行调整排序:AdjustMaxHeap()
(3) 进行堆排序,移除根结点,调整堆排序:HeapSort()
/** * 堆排序(大顶堆) * @param A 待排数组 */ public static void HeapSort(int [] A) { BuildMaxHeap(A); //创建堆 for (int i = A.length - 1; i > 0; i--) { //排序次数,须要len - l 趟 int temp = A[i]; //将堆顶元素(A[0])与数组末尾元素替换,更新待排数组长度 A[i] = A[0]; A[0] = temp; AdjustMaxHeap(A, 0, i); //调整新堆,对未排序数组再次进行调整 } } /** * 创建大顶堆 * @param A 待排数组 */ public static void BuildMaxHeap(int [] A) { for (int i = (A.length / 2) -1; i >= 0 ; i--) { //对[0,len/2]区间中的的结点(非叶结点)从下到上进行筛选调整 AdjustMaxHeap(A, i, A.length); } } /** * 调整大顶堆 * @param A 待排数组 * @param k 当前大顶堆根结点在数组中的下标 * @param len 当前待排数组长度 */ public static void AdjustMaxHeap(int [] A, int k, int len) { int temp = A[k]; for (int i = 2*k + 1; i < len; i = 2*i + 1) { //从最后一个叶结点开始从下到上进行堆调整 if (i + 1 < len && A[i] < A[i + 1]) { //比较两个子结点大小,取其大值 i++; } if (temp < A[i]) { //若结点大于父结点,将父结点替换 A[k] = A[i]; //更新数组下标,继续向上进行堆调整 k = i; } else { break; //若该结点小于父结点,则跳过继续向上进行堆调整 } } A[k] = temp; //将结点放入比较后应该放的位置 }
排序方法 | 空间复杂度 | 最好状况 | 最坏状况 | 平均时间复杂度 |
---|---|---|---|---|
简单选择排序 | O(1) | O(n^2) | O(n^2) | O(n^2) |
堆排序 | O(1) | O(log2n) | O(nlog2n) | O(nlog2n) |
归并排序是将多个有序表组合成一个新的有序表,该算法是采用分治法的一个典型的应用。即把待排序列分为若干个子序列,每一个子序列是有序的。而后再把有序子序列合并为一个总体有序的序列。这里主要以二路归并排序来进行分析。
该排序主要分为两步:
private static int[] aux; /** * 初始化辅助数组 aux * @param A 待排数组 */ public static void MergeSort(int [] A) { aux = new int[A.length]; MergeSort(A,0,A.length-1); } /** * 将数组分红两部分,以数组中间下标 mid 分为两部分依此递归 * 最后再将两部分的有序序列经过 Merge() 函数 合并 * @param A 待排数组 * @param low 数组起始下标 * @param high 数组末尾下标 */ public static void MergeSort (int[] A, int low, int high) { if (low < high) { int mid = (low + high) / 2; MergeSort(A, low, mid); MergeSort(A, mid + 1, high); Merge(A, low, mid, high); } } /** * 将 [low, mid] 有序序列和 [mid+1, high] 有序序列合并 * @param A 待排数组 * @param low 数组起始下标 * @param mid 数组中间分隔下标 * @param high 数组末尾下标 */ public static void Merge (int[] A, int low, int mid, int high) { int i, j, k; for (int t = low; t <= high; t++) { aux[t] = A[t]; } for ( i = low, j = mid + 1, k = low; i <= mid && j <= high; k++) { if(aux[i] < aux[j]) { A[k] = aux[i++]; } else { A[k] = aux[j++]; } } while (i <= mid) { A[k++] = aux[i++]; } while (j <= high) { A[k++] = aux[j++]; } }
基数排序比较特别,它是经过关键字数字各位的大小来进行排序。它是一种借助多关键字排序的思想来对单逻辑关键字进行排序的方法。
它主要有两种排序方法:
基数排序的思想:
/** * 找出数组中的最长位数 * @param A 待排数组 * @return MaxDigit 最长位数 */ public static int MaxDigit (int [] A) { if (A == null) { return 0; } int Max = 0; for (int i = 0; i < A.length; i++) { if (Max < A[i]) { Max = A[i]; } } int MaxDigit = 0; while (Max > 0) { MaxDigit++; Max /= 10; } return MaxDigit; } /** * 将基数排序的操做内化在一个二维数组中进行 * @param A 待排数组 */ public static void RadixSort(int [] A) { //建立一个二维数组,类比于在直角坐标系中,进行分配收集操做 int[][] buckets = new int[10][A.length]; int MaxDigit = MaxDigit(A); //t 用于提取关键字的位数 int t = 10; //按排序趟数进行循环 for (int i = 0; i < MaxDigit; i++) { //在一个桶中存放元素的数量,是buckets 二维数组的y轴 int[] BucketLen = new int[10]; //分配操做:将待排数组中的元素依此放入桶中 for (int j = 0; j < A.length ; j++) { //桶的下标值,是buckets 二维数组的x轴 int BucketIndex = (A[j] % t) / (t / 10); buckets[BucketIndex][BucketLen[BucketIndex]] = A[j]; //该下标下,也就是桶中元素个数随之增长 BucketLen[BucketIndex]++; } //收集操做:将已排好序的元素从桶中取出来 int k = 0; for (int x = 0; x < 10; x++) { for (int y = 0; y < BucketLen[x]; y++) { A[k++] = buckets[x][y]; } } t *= 10; } }
排序方法 | 空间复杂度 | 最好状况 | 最坏状况 | 平均时间复杂度 |
---|---|---|---|---|
归并排序 | O(n) | O(nlog2n) | O(nlog2n) | O(nlog2n) |
基数排序 | O(rd) | O(d(n+rd)) | O(d(n+rd)) | O(d(n+rd)) |
备注:基数排序中,n 为序列中的关键字数,d为关键字的关键字位数,rd 为关键字位数的个数