排序算法复杂度比较
快速排序
基准元素的选取会影响复杂度,最坏的情况可能到 O(n2)
- 选取区间起始元素
- 选取区间结束元素
- 在区间内随机选取一元素
!!!! 注意下面 这个,一定要先找右边,再找左边
// 在右边 找到第一个小于 pivot 的(所以大于【等于】的都忽略)
while (nums[right] >= pivot && left < right) {
right--;
}
// 在左边找到第一个大于 pivot 的(所以小于【等于】的都忽略)
while (nums[left] <= pivot && left < right) {
left++;
}
public class Sort_QuickSort { public static void main(String[] args) { int[] nums = new int[]{6,9,1,4,8,2,5,7,6}; Sort_QuickSort quickSort = new Sort_QuickSort(); quickSort.quickSort(nums, 0, nums.length-1); for (int i=0;i< nums.length;i++) { System.out.print(nums[i] + " "); } } public void quickSort(int[] nums, int startIndex, int endIndex) { if (startIndex >= endIndex) { return; } int pivotIndex = partition(nums, startIndex, endIndex); // pivot 左边的元素都比它小,右边的元素都比它大 // 递归对 pivot 左半做相同的操作 quickSort(nums, startIndex, pivotIndex-1); // 递归对 pivot 右半做相同的操作 quickSort(nums, pivotIndex+1, endIndex); } private int partition(int[] nums, int startIndex, int endIndex) { // 最后要达到的效果是,pivot 左边的元素都比它小,右边的元素都比它大 // 选取数组的第一个元素作为基准元素 pivot int pivotIndex = startIndex; int pivot = nums[pivotIndex]; // 左右指针 int left = startIndex; int right = endIndex; while (true) { // 在右边 找到第一个小于 pivot 的(所以大于【等于】的都忽略) while (nums[right] >= pivot && left < right) { right--; } // 在左边找到第一个大于 pivot 的(所以小于【等于】的都忽略) while (nums[left] <= pivot && left < right) { left++; } if (left < right) { // 交换左右两边 swap(nums, left, right); } else { // 退出循环 break; } } // 把最后的位置和 pivot 的位置交换 swap(nums, left, pivotIndex); // 返回最后的位置(现在放的是pivot) return left; } private void swap(int[] nums, int a, int b) { int tmp = nums[a]; nums[a] = nums[b]; nums[b] = tmp; } }
堆排序
前情:
- 完全二叉树是指从开头到结尾每个节点都有左右孩子,除了最后一个节点可以只有左孩子
- 完全二叉树可以用数组来表示
- 用数组表示的完全二叉树,某节点下标 i,那么它的左孩子下标是 2*i+1,右孩子下标是 2*i+2,父节点下标是 i/2-1
先把数组视为一个普通的二叉树,再把这个普通二叉树调整成大顶堆(从小到大排序就是大顶堆)
调整方法是 从 N/2-1 开始到堆顶0,依次调整:
每次调整一个节点,比较它和它的左右孩子,如果满足大根堆定义直接返回,如果不满足大根堆定义,把孩子中大的那个提上来。然后进行递归,继续调整这个节点。
然后是堆的取顶部最大
for(int i=N-1;i>0;i--)
// 交换最大的堆顶元素到堆尾,堆尾元素交换到了到了堆顶
swap(nums, 0, i)
// 堆的大小每次都减少1,即为 i,调整被换到堆顶的这个元素nums[0]
heapify(nums, i, 0)
public class Sort_HeapSort2 { public static void main(String[] args) { int[] nums = new int[]{6,9,1,4,8,2,5,7,6}; Sort_HeapSort2 heapSort = new Sort_HeapSort2(); heapSort.sort(nums); for (int i=0;i< nums.length;i++) { System.out.print(nums[i] + " "); } } public void sort(int nums[]) { int N = nums.length; // 数组中所有视作普通的完全二叉树,经过这个循环调整为大根堆(从小到大排序需要大根堆) // 从一半开始,这样包括孩子在内,可以把树中所有孩子都覆盖掉 // 注意是 i>=0 ,因为堆顶也可能不满足堆定义,要进行调整 for (int i = N / 2 - 1; i >= 0; i--) { heapify(nums, N, i); } // 依次把最大的元素从堆顶移到末尾 for (int i = N - 1; i > 0; i--) { // 先把大顶堆堆顶的最大元素和末尾的元素交换 swap(nums, 0, i); // 堆的大小每次都会减一,即为i,每次调整被推到堆顶的 nums[0]元素。使之满足堆定义 heapify(nums, i, 0); } } // To heapify a subtree rooted with node i which is // an index in arr[]. n is size of heap void heapify(int arr[], int N, int i) { int largest = i; // Initialize largest as root int l = 2 * i + 1; // left = 2*i + 1 int r = 2 * i + 2; // right = 2*i + 2 // If left child is larger than root if (l < N && arr[l] > arr[largest]) largest = l; // If right child is larger than largest so far if (r < N && arr[r] > arr[largest]) largest = r; // If largest is not root // 当前的不是在它左右孩子中最大的 if (largest != i) { // 交换当前的和孩子中比它大的 swap(arr, i, largest); // 递归,继续调整这个当前的元素(现在被换到了largest的位置)下沉到合适的位置 heapify(arr, N, largest); } } private void swap(int[] nums, int a, int b) { int tmp = nums[a]; nums[a] = nums[b]; nums[b] = tmp; } }
还有一种就是,添加元素到堆的时候,并不把全部的元素视作一个普通二叉树,然后把这个普通二叉树从 N/2-1~0 调整成堆
而是从一个元素开始,逐个把元素添加到堆的末尾,再调整这个元素使之上浮到合适的位置;再加入堆末尾,再调整...........整个过程中都是完整的堆
形成大顶堆后,从堆中取出元素的操作和前面一样
public class Sort_HeapSort { public static void main(String[] args) { int[] nums = new int[]{6,9,1,4,8,2,5,7,6}; Sort_HeapSort heapSort = new Sort_HeapSort(); heapSort.sort(nums); for (int i=0;i< nums.length;i++) { System.out.print(nums[i] + " "); } } public void sort(int[] nums) { // 从小到大排序需要构建大根堆,然后每次把大根堆堆顶即 nums[0] 位置最大的元素放到末尾去 for (int i=1;i<nums.length-1;i++) { // 构造大根堆:0~i是大根堆, i~末尾是未调整的元素。所以i要递增 add2Heap(nums, i); } // 然后每次把大根堆 0 位置最大的元素放到末尾去 for (int i=nums.length-1;i>0;i--) { // 依次取出堆顶元素:0~i是大根堆, i~末尾是之前取出来的堆顶最大元素。所以i要递减 getTopFromHeap(nums, i); } } private void swap(int[] nums, int a, int b) { int tmp = nums[a]; nums[a] = nums[b]; nums[b] = tmp; } public void add2Heap(int[] nums, int endIndex) { // 0~endIndex-1 都是已经堆化好的。现在将 endIndex 位置的元素加入 int thisElemIndex = endIndex; // 完全二叉树,位置为 i 的元素的父节点的位置是 (i-1)/2 int parentIndex = (thisElemIndex - 1)/2; // 如果 this 比它的 parent 大,就交换他们两个,直到 this 到合适的位置(比它的parent小就停下来) while (nums[parentIndex] < nums[thisElemIndex] && parentIndex>=0) { swap(nums, parentIndex, thisElemIndex); thisElemIndex = parentIndex; parentIndex = (thisElemIndex - 1)/2; } } public void getTopFromHeap(int[] nums, int endIndex) { // endIndex ~ 末尾 都是已经取出来的大堆顶。现在把堆末尾 endIndex 的那个元素放到堆顶 0, 然后调整成大根堆 swap(nums, 0, endIndex); int thisIndex = 0; // parent 要比它两个孩子都要小。因此校验这个,如果不满足,找到它需要交换的孩子的坐标 // 堆顶最大元素 nums[0] 被放到了 nums[endIndex],因此大顶堆边界缩小到了 endIndex-1 int swapChildIndex = getSwapChildIndex(nums, endIndex-1, thisIndex); while (swapChildIndex != -1) { // 与需要交换的孩子进行交换 swap(nums, thisIndex, swapChildIndex); // 继续调整,直到比两个孩子都小 thisIndex = swapChildIndex; swapChildIndex = getSwapChildIndex(nums, endIndex-1, thisIndex); } } public int getSwapChildIndex(int[] nums, int endIndex, int parentIndex) { int leftChildIndex = 2*parentIndex + 1; int rightChildIndex = 2*parentIndex + 2; // 没有左孩子(更没有右孩子) if (leftChildIndex > endIndex) { return -1; } // 只有左孩子 else if (rightChildIndex > endIndex) { // 不满足比左孩子大的话, 就要交换 return nums[parentIndex] >= nums[leftChildIndex]? -1:leftChildIndex; } // 因为是完全二叉树,所以没有【没有左孩子而只有右孩子】的情况 // 同时有左孩子和右孩子 else { // 比两个孩子都大,不用交换 if (nums[parentIndex] >= nums[leftChildIndex] && nums[parentIndex] >= nums[rightChildIndex]) { return -1; } // 否则和两个孩子中较大的那个交换 else { return nums[leftChildIndex] > nums[rightChildIndex] ? leftChildIndex : rightChildIndex; } } } }
归并排序
public class Sort_MergeSort { // Driver code public static void main(String args[]) { int arr[] = { 8,4,1,3,9,2,5 }; Sort_MergeSort ob = new Sort_MergeSort(); // !!! 注意方法里是要包含 r 的,所以这里是 nums.length-1 而不是 nums.length ob.sort(arr, 0, arr.length - 1); } // Main function that sorts arr[l..r] using // merge() void sort(int arr[], int l, int r) { if (l >= r) { return; } // Find the middle point int m = l + (r - l) / 2; // Sort first and second halves // 递归左右两半。左半包括m,右半不包括 sort(arr, l, m); sort(arr, m + 1, r); // Merge the sorted halves // 合并两个有序子数组。这个一定要在递归划分的后面 merge(arr, l, m, r); } // Merges two subarrays of arr[]. // First subarray is arr[l..m] // Second subarray is arr[m+1..r] void merge(int arr[], int l, int m, int r) { // Find sizes of two subarrays to be merged // 左半数组的长度 int n1 = m - l + 1; // 右半数组的长度 int n2 = r - m; // Create temp arrays // 初始化临时数组 L R int L[] = new int[n1]; int R[] = new int[n2]; // Copy data to temp arrays // 把左右两半的数组拷贝到临时数组 L R for (int i = 0; i < n1; ++i) { L[i] = arr[l + i]; } for (int j = 0; j < n2; ++j) { // 注意 arr[m+1+j] R[j] = arr[m + 1 + j]; } // Merge the temp arrays // Initial indices of first and second subarrays int i = 0, j = 0; // Initial index of merged subarray array int k = l; /* * arr[l:r] = [1,3,4,8,2,5,9] * 左右都是有序的了 * L[0:n1-1] = arr[l:m] = [1,3,4,8] * R[0:n2-1] = arr[m:r] = [2,5,9] * if(1<2) arr[l:r] = ["1",3,4,8,2,5,9] * if(3<2) else arr[l:r] = ["1,2",4,8,2,5,9] * if(3<5) arr[l:r] = ["1,2,3",8,2,5,9] * if(4<5) arr[l:r] = ["1,2,3,4",2,5,9] * if(8<5) else arr[l:r] = ["1,2,3,4,5",5,9] * if(8<9) else arr[l:r] = ["1,2,3,4,5,8",9] */ while (i < n1 && j < n2) { if (L[i] <= R[j]) { arr[k] = L[i]; // i++ 要放在 if (L[i] <= R[j]) 内层 i++; } else { arr[k] = R[j]; j++; } k++; }/* * arr[l:r] = [1,3,4,8,2,5,9] * 左右都是有序的了 * L[0:n1-1] = arr[l:m] = ["1,3,4,8"] * R[0:n2-1] = arr[m:r] = ["2,5",9] * arr[l:r] = ["1,2,3,4,5,8",9] * L都走完了,因此不走这里 */ // Copy remaining elements of L[] if any while (i < n1) { arr[k] = L[i]; i++; k++; }/* * arr[l:r] = [1,3,4,8,2,5,9] * 左右都是有序的了 * L[0:n1-1] = arr[l:m] = ["1,3,4,8"] * R[0:n2-1] = arr[m:r] = ["2,5",9] * arr[l:r] = ["1,2,3,4,5,8",9] * 把R中剩下的9复制进去 arr[l:r] = ["1,2,3,4,5,8,9“] */ // Copy remaining elements of R[] if any while (j < n2) { arr[k] = R[j]; j++; k++; } } // A utility function to print array of size n static void printArray(int arr[]) { int n = arr.length; for (int i = 0; i < n; ++i) System.out.print(arr[i] + " "); System.out.println(); } /* This code is contributed by Rajat Mishra */ }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· DeepSeek “源神”启动!「GitHub 热点速览」
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器