左程云算法初级班 第二课
-
荷兰国旗问题
问题一
给定一个数组arr,和一个数num,请把小于等于num的数放在数组的左边,大于num的数放在数组的右边。
要求额外空间复杂度O(1),时间复杂度O(N)
问题二(荷兰国旗问题)
给定一个数组arr,和一个数num,请把小于num的数放在数组的左边,等于num的数放在数组的中间,大于num的数放在数组的
右边。
要求额外空间复杂度O(1),时间复杂度O(N)- 对于问题一:预设一个(<=num)边界位置less为-1,从0位置开始往后遍历数组,若值<=num,则将其与less+1互换,同时将less值更新为less+1,直到数组遍历结束。
- 对于问题二:预设两个边界位置,分别为(<num)边界位置less为-1和(>num)边界位置more为arr.length,从curr=0位置开始往后遍历数组,若值<num,则将其与less+1互换,同时将less值更新为less+1并且curr加1;若值>num,则将其与more-1互换,同时将more值更新为more-1,curr不变。直到curr和more相遇。
- 荷兰国旗问题代码:
public static int[] partition(int[] arr, int l, int r, int p) { int less = l - 1; int more = r + 1; while (l < more) { if (arr[l] < p) { swap(arr, ++less, l++); } else if (arr[l] > p) { swap(arr, --more, l); } else { l++; } } return new int[] { less + 1, more - 1 }; } // for test public static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; }
-
- 传统的快速排序在数组内有相同数值的情况时做出了多余的常数操作,用荷兰国旗问题对快速排序改进可减小该常数操作。
- 为避免极端情况,在取中间参考数值num时,采用随机的方式在数组中取出一个数值num,因此称其为随机快速排序。
- 随机快速排序在长期期望下的时间复杂度O(N*logN),额外空间复杂度O(logN);额外空间的复杂度是存储迭代运算时的数组分界点时产生的。
- 随机快速排序代码如下:
public static void quickSort(int[] arr) { if (arr == null || arr.length < 2) { return; } quickSort(arr, 0, arr.length - 1); } public static void quickSort(int[] arr, int l, int r) { if (l < r) { swap(arr, l + (int) (Math.random() * (r - l + 1)), r); int[] p = partition(arr, l, r); quickSort(arr, l, p[0] - 1); quickSort(arr, p[1] + 1, r); } } public static int[] partition(int[] arr, int l, int r) { int less = l - 1; int more = r; while (l < more) { if (arr[l] < arr[r]) { swap(arr, ++less, l++); } else if (arr[l] > arr[r]) { swap(arr, --more, l); } else { l++; } } swap(arr, more, r); return new int[] { less + 1, more }; } public static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; }
-
堆排序
-
使用数组实现二叉树:对于任一结点i,左孩子结点为2×i+1:,右孩子结点为:2×i+2,父结点为:(i-1)/2。
-
堆:(英语:heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象;物理特性为一棵完全二叉树;
-
大根堆:根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最大者;
-
小根堆:根节点的关键字既大于或等于左子树的关键字值,又大于或等于右子树的关键字值。
堆结构非常重要
1,堆结构的heapInsert与heapify
2,堆结构的增大和减少
3,如果只是建立堆的过程,时间复杂度为O(N)
4,优先级队列结构,就是堆结构- 堆结构的heapInsert:将一个初始数组建立大根堆(小根堆)结构,入参是孩子结点;堆结构的heapify:若堆结构变化,该函数将堆结构调整为大根堆(小根堆)结构,入参是根节点。
- 堆结构的heapInsert与heapify代码:
public static void heapInsert(int[] arr, int index) { while (arr[index] > arr[(index - 1) / 2]) { swap(arr, index, (index - 1) / 2); index = (index - 1) / 2; } } public static void heapify(int[] arr, int index, int size) { int left = index * 2 + 1; while (left < size) { int largest = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left; largest = arr[largest] > arr[index] ? largest : index; if (largest == index) { break; } swap(arr, largest, index); index = largest; left = index * 2 + 1; } } public static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; }
- 堆排序思路:将数组调整为大根堆,然后将根节点与数组最后一位交换并将堆得size减1,并将新的堆调整为大根堆重复前步骤直到size为0。
- 堆排序的时间复杂度O(N*logN),额外空间复杂度O(1)
- 堆排序代码:
public static void heapSort(int[] arr) { if (arr == null || arr.length < 2) { return; } for (int i = 0; i < arr.length; i++) { heapInsert(arr, i); } int size = arr.length; swap(arr, 0, --size); while (size > 0) { heapify(arr, 0, size); swap(arr, 0, --size); } }
-
排序算法的稳定性
- 稳定性:在排序后,若相等的数值的相对位置不改变,则该算法视为稳定的。
- 冒泡排序:可做到稳定的,当遇到相等的数值时不交换;
- 插入排序:可做到稳定的,当遇到相等的数值时不交换;
- 选择排序:无法做到稳定;
- 归并排序:可做到稳定的,当遇到相等的数值先拷贝左边数组中的值;
- 快速排序:无法做到稳定;
- 堆排序:无法做到稳定。
- 工程中的综合排序算法:
1.对于5种基本数据类型则使用快速排序,这是因为基本数据类型不用区分数值前后顺序,即不用考虑稳定性;
2.对于自己定义的数据类型,例如Student类,则使用归并排序,这是因为该情况下得考虑稳定性因素;
3.长度<60时,使用插入排序,这是因为在长度N为60以内,插入排序的时间复杂度为O(N²)的劣势体现不出来,反而插入排序常数项很低,导致在小样本情况下,插入排序极快。 -
桶排序、计数排序、基数排序的介绍
1,非基于比较的排序,与被排序的样本的实际数据状况很有关系,所以实际中并不经常使用
2,时间复杂度O(N),额外空间复杂度O(N)
3,稳定的排序- 补充问题:
给定一个数组,求如果排序之后,相邻两数的最大差值,要求时间复杂度O(N),且要求不能用非基于比较的排序。
- 解题思路:
1.若是数组有N个数,则准备N+1个桶,然后找到数组中的最小值min与最大值max分别放入0号桶和N号桶,将min与max中间的值等分(N-1)份作为中间(N-1)个桶的范围;
2.由于一共N个数,有N+1和桶,故可以断定相邻数最大差值必定不在同一个桶内;
3.每个桶记录桶里的最大值与最小值,并给一个标志位flag来标记桶内是否有数值;
4.则可推断,相邻数最大差值必定为所有非空桶的最小值与前一非空桶的最大值的差值中的最大值。- 解题代码:
public static int maxGap(int[] nums) { if (nums == null || nums.length < 2) { return 0; } int len = nums.length; int min = Integer.MAX_VALUE; int max = Integer.MIN_VALUE; for (int i = 0; i < len; i++) { min = Math.min(min, nums[i]); max = Math.max(max, nums[i]); } if (min == max) { return 0; } boolean[] hasNum = new boolean[len + 1]; int[] maxs = new int[len + 1]; int[] mins = new int[len + 1]; int bid = 0; for (int i = 0; i < len; i++) { bid = bucket(nums[i], len, min, max); mins[bid] = hasNum[bid] ? Math.min(mins[bid], nums[i]) : nums[i]; maxs[bid] = hasNum[bid] ? Math.max(maxs[bid], nums[i]) : nums[i]; hasNum[bid] = true; } int res = 0; int lastMax = maxs[0]; int i = 1; for (; i <= len; i++) { if (hasNum[i]) { res = Math.max(res, mins[i] - lastMax); lastMax = maxs[i]; } } return res; } public static int bucket(long num, long len, long min, long max) { return (int) ((num - min) * len / (max - min)); }