目录
排序算法
- 稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面
- 不稳定:如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面
- 内排序:在排序期间数据对象全部存放在内存的排序
- 外排序:在排序期间全部对象太多,不能同时存放在内存中,根据排序过程的要求,不断在内、外存间移动的排序
- 时间复杂度: 一个算法执行所耗费的时间
- 空间复杂度:运行完一个程序所需内存的大小
冒泡排序
排序原理
排序图解
代码实现
//冒泡排序 for (int i = 0; i < arr.length - 1; i++) { for (int j = 0; j < arr.length - 1 - i; j++) { if (arr[j] > arr[j + 1]) { int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } }
选择排序
排序原理
排序图解
代码实现
//选择排序 for (int i = 0; i < arr.length; i++) { for (int j = i + 1; j < arr.length; j++) { if (arr[j] < arr[i]) { int temp = arr[j]; arr[j] = arr[i]; arr[i] = temp; } } }
插入排序
排序原理
排序图解
代码实现
//插入排序 for (int i = 0; i < arr.length ; i++) { int temp = arr[i],j; for (j = i-1; j >= 0 ; j--) { if (arr[j]>temp){ arr[j+1] = arr[j]; }else { break; } } arr[j+1] = temp; }
希尔排序
排序原理
插入排序的一种进阶排序算法,通过一个不断缩小的增量序列,对无序序列反复的进行拆分并且对拆分后的序列使用插入排序的一种算法,所以也叫作 ”缩小增量排序” 或者 “递减增量排序”。
序列按步长 gap 分组,对每组记录采用直接插入排序方法进行排序。随着步长逐渐减小,所分成的组包含的记录越来越多,当步长的值减小到 1 时,整个数据合成为一组有序记录,完成排序。
排序图解
代码实现
//希尔排序 public static int[] ShellSort(int[] array) { //定义中间变量temp,增量gap int temp, gap = array.length / 2; while (gap > 0) { //从第gap个元素依次对其所在的组 for (int i = gap; i < array.length; i++) { //要插入的元素 temp = array[i]; //定义前边待插入位置的下标 int preIndex = i - gap; //判断数值大小、交换位置 while (preIndex >= 0 && array[preIndex] > temp) { array[preIndex + gap] = array[preIndex]; preIndex -= gap; } array[preIndex + gap] = temp; } //更新增量 gap /= 2; } return array; }
归并排序
排序原理
建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
排序图解
代码实现
//归并排序 public static int[] MergeSort(int[] array) { if (array.length < 2) return array; //计算中间下标,分成两个数组 int mid = array.length / 2; /* copyOfRange(int []original,int from,int to),original为int型数组,from为开始角标值,to为终止角标值。 (其中包括from角标,不包括to角标。即[from,to) */ int[] left = Arrays.copyOfRange(array, 0, mid); int[] right = Arrays.copyOfRange(array, mid, array.length); //合并数组 return merge(MergeSort(left), MergeSort(right)); } //将两段数组结合成一个排序数组 public static int[] merge(int[] left, int[] right) { //定义结果数组 int[] result = new int[left.length + right.length]; for (int index = 0, i = 0, j = 0; index < result.length; index++) { //判断并赋值给结果数组 if (i >= left.length) result[index] = right[j++]; else if (j >= right.length) result[index] = left[i++]; else if (left[i] > right[j]) result[index] = right[j++]; else result[index] = left[i++]; } return result; }
快速排序
排序原理
通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据比另一部分的所有数据要小,再按这种方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,使整个数据变成有序序列。
排序图解
代码实现
//快速排序 public static void Quick_Sort(int[] arr, int begin, int end) { if (begin > end) return; //定义基准数、索引值 int temp = arr[begin]; int i = begin; int j = end; while (i != j) { //以基准数为基准,为基准数左右数值排序 while (arr[j] >= temp && j > i) j--; while (arr[i] <= temp && j > i) i++; if (j > i) { int t = arr[i]; arr[i] = arr[j]; arr[j] = t; } } //更新基准数 arr[begin] = arr[i]; arr[i] = temp; //递归 Quick_Sort(arr, begin, i - 1); Quick_Sort(arr, i + 1, end); }
堆排序
排序原理
堆的结构可以分为大根堆和小根堆,是一个完全二叉树,而堆排序是根据堆的这种数据结构设计的一种排序。每个结点的值都大于其左孩子和右孩子结点的值,称为大根堆;每个结点的值都小于其左孩子和右孩子结点的值,称为小根堆。如图:
- 将待排序的数组构造成一个大根堆,整个数组的最大值就是堆结构的顶端
- 将顶端的数与末尾的数交换,末尾的数为最大值,剩余待排序数组个数为n-1
- 将剩余的n-1个数再构造成大根堆,再将顶端数与n-1位置的数交换,如此反复执行,得到有序数组
堆排序实现步骤
第一步:将无序数组调整为完全二叉树,第一个位置也就是数组下标为 0 的地方,计算 index = length / 2-1
第二步:依次比较 index索引后边的数值,然后交换(从最后一个索引值开始比较,自行选择是构造大根堆还是小根堆)
第三步:比较到 index后重新计算 index的值,再依次比较、交换数值(index的值是不断变化的)
示例:
排序图解
代码实现(参考而来)
//堆排序 public static void HeapSort(int[] array) { // 按照完全二叉树的特点,从最后一个非叶子节点开始,对于整棵树进行大根堆的调整 // 也就是说,是按照自下而上,每一层都是自右向左来进行调整的 // 注意,这里元素的索引是从0开始的 // 另一件需要注意的事情,这里的建堆,是用堆调整的方式来做的 // 堆调整的逻辑在建堆和后续排序过程中复用的 for (int i = array.length / 2 - 1; i >= 0; i--) { adjustHeap(array, i, array.length); } // 上述逻辑,建堆结束 // 下面,开始排序逻辑 for (int j = array.length - 1; j > 0; j--) { // 元素交换 // 说是交换,其实质就是把大顶堆的根元素,放到数组的最后;换句话说,就是每一次的堆调整之后,都会有一个元素到达自己的最终位置 swap(array, 0, j); // 元素交换之后,毫无疑问,最后一个元素无需再考虑排序问题了。 // 接下来我们需要排序的,就是已经去掉了部分元素的堆了,这也是为什么此方法放在循环里的原因 // 而这里,实质上是自上而下,自左向右进行调整的 adjustHeap(array, 0, j); } } //移动元素 public static void adjustHeap(int[] array, int i, int length) { // 先把当前元素取出来,因为当前元素可能要一直移动 int temp = array[i]; // 可以参照sort中的调用逻辑,在堆建成,且完成第一次交换之后,实质上i=0;也就是说,是从根所在的最小子树开始调整的 // 接下来的讲解,都是按照i的初始值为0来讲述的 // 这一段很好理解,如果i=0;则k=1;k+1=2 // 实质上,就是根节点和其左右子节点记性比较,让k指向这个不超过三个节点的子树中最大的值 // 这里,必须要说下为什么k值是跳跃性的。 // 首先,举个例子,如果a[0] > a[1]&&a[0]>a[2],说明0,1,2这棵树不需要调整,那么,下一步该到哪个节点了呢?肯定是a[1]所在的子树了, // 也就是说,是以本节点的左子节点为根的那棵小的子树 // 而如果a[0}<a[2]呢,那就调整a[0]和a[2]的位置,然后继续调整以a[2]为根节点的那棵子树,而且肯定是从左子树开始调整的 // 所以,这里面的用意就在于,自上而下,自左向右一点点调整整棵树的部分,直到每一颗小子树都满足大根堆的规律为止 for (int k = 2 * i + 1; k < length; k = 2 * k + 1) { // 让k先指向子节点中最大的节点 if (k + 1 < length && array[k] < array[k + 1]) { k++; } // 如果发现子节点更大,则进行值的交换 if (array[k] > temp) { swap(array, i, k); // 下面就是非常关键的一步了 // 如果子节点更换了,那么,以子节点为根的子树会不会受到影响呢? // 所以,循环对子节点所在的树继续进行判断 i = k; // 如果不用交换,那么,就直接终止循环了 } else { break; } } } //交换元素 public static void swap(int[] arr, int a, int b) { int temp = arr[a]; arr[a] = arr[b]; arr[b] = temp; }