快排+堆排序
- 荷兰国旗问题
- 经典快排、改进快排、随机快排
- 堆结构
(from左神算法初级班第二节)
1.荷兰国旗问题
问题一:
给定一个数组arr,和一个数num,请把小于等于num的数放在数 组的左边,大于num的数放在数组的右边。
要求额外空间复杂度O(1),时间复杂度O(N)
问题二(荷兰国旗问题) 给定一个数组arr,和一个数num,请把小于num的数放在数组的 左边,等于num的数放在数组的中间,大于num的数放在数组的 右边。
要求额外空间复杂度O(1),时间复杂度O(N)
荷兰国旗问题代码:
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++);//less位置和l位置交换,并且less扩充一个位置
} 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;
}
2.经典快排、改进快排(荷兰国旗问题)
优点:常数项很低、常数项操作很少
时间复杂度O(N*logN)
额外空间复杂度O(logN)(需要记录划分点,也就是树结构)
数据状况糟糕的话,时间复杂度会变成O(N2)
1)原理:以最后一个数x为界,小于x放左边,大于放x右边。x左边再以最后一个数y为界,小于y放在y左边,大于y放在y右边,一直递归下去。
2)代码:
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;
}
3)随机快排
随机在数组上选一个数,然后和最后一个数交换,再进行快排。
长期期望时间复杂度为:O(N*logN)
swap(arr, l + (int) (Math.random() * (r - l + 1)), r);//加一行就是随机快排
3.堆结构(可以转换成数组)
1)什么是完全二叉树?
任何一个非叶子节点是完全二叉树
二叉树求下标关系:
二叉树 | 下标 |
---|---|
父节点树下标 | (i-1)/2 |
左孩子树下标 | 2*i+1 |
右孩子树下标 | 2*i+2 |
例如:该数组找到其父节点和左右孩子树
其树结构为:
2)大根堆、小根堆
大根堆:树的最大值在头结点(整棵树)
小根堆:树的最小值在头结点(整棵树)
建立大根堆:
public static void heapInsert(int[] arr, int index) {
while (arr[index] > arr[(index - 1) / 2]) {//比父节点大。来到0位置会停
swap(arr, index, (index - 1) / 2);//交换
index = (index - 1) / 2;//来到父位置
}
}
建立大根堆的时间复杂度为:O(logN),因为是完全二叉树形成的高度
3)堆上最大值发生变化,需要调整大根堆时:
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;
}
}
4)堆结构完整代码:
public static void heapSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int i = 0; i < arr.length; i++) {
heapInsert(arr, i);//0~i形成大根堆
}
}
public static void heapInsert(int[] arr, int index) {
while (arr[index] > arr[(index - 1) / 2]) {//比父节点大。来到0位置会停
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
largest = arr[largest] > arr[index] ? largest : index;//largest和我比较,谁大谁做largest
if (largest == index) {
break;//最大是我,就不用调整了,不往下沉了,并且跳出循环
}
swap(arr, largest, index);//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;
}
5)用堆结构求中位数问题(时间复杂度为:O(logN)):
- 建立一个大根堆、一个小根堆,两个堆数目差值大于1,就将多的堆,拿出一个最上的数值,扔到另一个堆中(减堆操作、加堆操作)
- 一开始先往小根堆里加一个数,然后下个数比这个数大就进大根堆,小就进入小根堆排序,并进行一次换堆操作(因为差值大于1)
- 减堆操作:先将堆的最后一个数和堆顶交换,然后进行堆调整,最大堆还是最大堆,最小堆还是最小堆,只是size-1;size位置上就是要加到另一个堆上的数。
- 加堆操作:
最大堆–>最小堆:size上的数直接做堆顶。
最小堆–>最大堆:size上的数直接做最后一个数。
代码:
public static void heapSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int i = 0; i < arr.length; i++) {
heapInsert(arr, i);//0~i形成大根堆
}
int size = arr.length;
swap(arr, 0, --size);
while (size > 0) {
heapify(arr, 0, size);
swap(arr, 0, --size);
}
}
public static void heapInsert(int[] arr, int index) {
while (arr[index] > arr[(index - 1) / 2]) {//比父节点大。来到0位置会停
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
largest = arr[largest] > arr[index] ? largest : index;//largest和我比较,谁大谁做largest
if (largest == index) {
break;//最大是我,就不用调整了,不往下沉了,并且跳出循环
}
swap(arr, largest, index);//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;
}