length/2
),对每组使用直接插入排序
// 希尔排序时, 对有序序列在插入时采用交换法 public static void shellSort(int[] arr) { int temp = 0; int count = 0; // 根据前面的逐步分析,使用循环处理 for (int gap = arr.length / 2; gap > 0; gap /= 2) { for (int i = gap; i < arr.length; i++) { // 遍历各组中所有的元素(共gap组,每组有个元素), 步长gap for (int j = i - gap; j >= 0; j -= gap) { // 如果当前元素大于加上步长后的那个元素,说明交换 if (arr[j] > arr[j + gap]) { temp = arr[j]; arr[j] = arr[j + gap]; arr[j + gap] = temp; } } } } }
//对交换式的希尔排序进行优化->移位法 public static void shellSort2(int[] arr) { // 增量gap, 并逐步的缩小增量 for (int gap = arr.length / 2; gap > 0; gap /= 2) { // 从第gap个元素,逐个对其所在的组进行直接插入排序 for (int i = gap; i < arr.length; i++) { int j = i; int temp = arr[j]; if (arr[j] < arr[j - gap]) { while (j - gap >= 0 && temp < arr[j - gap]) { //移动 arr[j] = arr[j-gap]; j -= gap; } //当退出while后,就给temp找到插入的位置 arr[j] = temp; } } } }
//分+合方法 public static void mergeSort(int[] arr, int left, int right, int[] temp) { if(left < right) { int mid = (left + right) / 2; //中间索引 //看似是分解其实都数都在arr中 只是 下标在分解 //向左递归进行分解 mergeSort(arr, left, mid, temp); //向右递归进行分解 mergeSort(arr, mid + 1, right, temp); //递归到每一个数分开时,再将每个数一次合并再合并 //比如8个数,先将12,34,56,78依次合并 再将1234,5678依次合并。。。。0 //每次合并后都会把合并好的数从temp拷贝到arr中,合并多少就拷贝多少 merge(arr, left, mid, right, temp); } } //合并的方法 /** * * @param arr 排序的原始数组 * @param left 左边有序序列的初始索引 * @param mid 中间索引 * @param right 右边索引 * @param temp 做中转的数组 */ public static void merge(int[] arr, int left, int mid, int right, int[] temp) { int i = left; // 初始化i, 左边有序序列的初始索引 int j = mid + 1; //初始化j, 右边有序序列的初始索引 int t = 0; // 指向temp数组的当前索引 //(一) //先把左右两边(有序)的数据按照规则填充到temp数组 //直到左右两边的有序序列,有一边处理完毕为止 while (i <= mid && j <= right) {//继续 //如果左边的有序序列的当前元素,小于等于右边有序序列的当前元素 //即将左边的当前元素,填充到 temp数组 //然后 t++, i++ if(arr[i] <= arr[j]) { temp[t] = arr[i]; t += 1; i += 1; } else { //反之,将右边有序序列的当前元素,填充到temp数组 temp[t] = arr[j]; t += 1; j += 1; } } //(二) //把有剩余数据的一边的数据依次全部填充到temp while( i <= mid) { //左边的有序序列还有剩余的元素,就全部填充到temp temp[t] = arr[i]; t += 1; i += 1; } while( j <= right) { //右边的有序序列还有剩余的元素,就全部填充到temp temp[t] = arr[j]; t += 1; j += 1; } //(三) //将temp数组的元素拷贝到arr //注意,并不是每次都拷贝所有 t = 0; int tempLeft = left; // //第一次合并 tempLeft = 0 , right = 1 // tempLeft = 2 right = 3 // tL=0 ri=3 //最后一次 tempLeft = 0 right = 7 while(tempLeft <= right) { arr[tempLeft] = temp[t]; t += 1; tempLeft += 1; } }
//基数排序方法 public static void radixSort(int[] arr) { //根据前面的推导过程,我们可以得到最终的基数排序代码 //1. 得到数组中最大的数的位数 int max = arr[0]; //假设第一数就是最大数 for(int i = 1; i < arr.length; i++) { if (arr[i] > max) { max = arr[i]; } } //得到最大数是几位数 int maxLength = (max + "").length(); //定义一个二维数组,表示10个桶, 每个桶就是一个一维数组 //说明 //1. 二维数组包含10个一维数组 //2. 为了防止在放入数的时候,数据溢出,则每个一维数组(桶),大小定为arr.length //3. 名明确,基数排序是使用空间换时间的经典算法 int[][] bucket = new int[10][arr.length]; //为了记录每个桶中,实际存放了多少个数据,我们定义一个一维数组来记录各个桶的每次放入的数据个数 //可以这里理解 //比如:bucketElementCounts[0] , 记录的就是 bucket[0] 桶的放入数据个数 int[] bucketElementCounts = new int[10]; //这里我们使用循环将代码处理 for(int i = 0 , n = 1; i < maxLength; i++, n *= 10) { //(针对每个元素的对应位进行排序处理), 第一次是个位,第二次是十位,第三次是百位.. for(int j = 0; j < arr.length; j++) { //取出每个元素的对应位的值 int digitOfElement = arr[j] / n % 10; //放入到对应的桶中 bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j]; bucketElementCounts[digitOfElement]++; } //按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组) int index = 0; //遍历每一桶,并将桶中是数据,放入到原数组 for(int k = 0; k < bucketElementCounts.length; k++) { //如果桶中,有数据,我们才放入到原数组 if(bucketElementCounts[k] != 0) { //循环该桶即第k个桶(即第k个一维数组), 放入 for(int l = 0; l < bucketElementCounts[k]; l++) { //取出元素放入到arr arr[index++] = bucket[k][l]; } } //第i+1轮处理后,需要将每个 bucketElementCounts[k] = 0 !!!! bucketElementCounts[k] = 0; } //System.out.println("第"+(i+1)+"轮,对个位的排序处理 arr =" + Arrays.toString(arr)); } }
时间复杂度O(nlogn) 将一个数组看成二叉树来比较,顺序存储
大顶堆:每个结点的值都大于等于左右孩子的值
小顶堆:每个结点的值都小于等于左右孩子的值
构成大顶堆
arr.lrngth/2-1
= 5/2 = 1交换
继续满足大顶堆
//编写一个堆排序的方法 public static void heapSort(int arr[]) { int temp = 0; System.out.println("堆排序!!"); for(int i = arr.length / 2 -1; i >=0; i--) { adjustHeap(arr, i, arr.length); } /* * 2).将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端; 3).重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。 */ for(int j = arr.length-1;j >0; j--) { //交换 temp = arr[j]; arr[j] = arr[0]; arr[0] = temp; adjustHeap(arr, 0, j); } } //将一个数组(二叉树), 调整成一个大顶堆 /** * @param arr 待调整的数组 * @param i 表示非叶子结点在数组中索引 * @param lenght 表示对多少个元素继续调整, length 是在逐渐的减少 * 功能: 完成 将 以 i 对应的非叶子结点的树调整成大顶堆 * 举例 int arr[] = {4, 6, 8, 5, 9}; => i = 1 => adjustHeap => 得到 {4, 9, 8, 5, 6} * 如果我们再次调用 adjustHeap 传入的是 i = 0 => 得到 {4, 9, 8, 5, 6} => {9,6,8,5, 4} */ public static void adjustHeap(int arr[], int i, int lenght) { int temp = arr[i];//先取出当前元素的值,保存在临时变量 //开始调整 //说明 //1. k = i * 2 + 1 k 是 i结点的左子结点 for(int k = i * 2 + 1; k < lenght; k = k * 2 + 1) { if(k+1 < lenght && arr[k] < arr[k+1]) { //说明左子结点的值小于右子结点的值 k++; // k 指向右子结点 } if(arr[k] > temp) { //如果子结点大于父结点 arr[i] = arr[k]; //把较大的值赋给当前结点 i = k; //!!! i 指向 k,继续循环比较 } else { break;//! 只需要调整 以i为结点的左右子结点,那为什么还要循环呢? //因为后面我们在执行-最大元素"沉"到数组末端的-时候 需要将最小的那个值放到根结点 所以需要循环又把它放到它该有的位置 //这里肯定是i=0开始,因为他已经是一个大顶堆了,所以可以循环将它放入该有的位置 //比如 {4,3,5,8,9} 我们以i=0开始发现会排序成=>{5,3,4,8,9}原因就是他不是大顶堆,所以break、 } } //当for 循环结束后,我们已经将以i 为父结点的树的最大值,放在了 最顶(局部) arr[i] = temp;//将temp值放到调整后的位置 }
相关术语解释
笔记来源:B站 尚硅谷 韩老师