1.荷兰国旗问题
定一个数组arr,和一个数num,请把小于num的数放在数组的左边,等于num的数放在数组的中间,大于num的数放在数组的右边。
要求时间复杂度为O(N)、额外空间复杂度为O(1)。
分析:三个指针法:一个指向前头less,一个指向尾部more,一个是当前下标cur。当前下标由指向前面的指针推着前进。
import java.util.Arrays;
public class FlagOfHolland {
public static int[] partition(int[] array,int L,int R,int num){
int less=L-1;
int more=R+1;
int cur=L;
while(cur<more){
if(array[cur]<num){
swap(array,cur++,++less);
}else if(array[cur]>num){
swap(array,cur,--more);
}else{
cur++;
}
}
return array;
}
public static void swap(int[] array,int m,int n){
int temp=array[m];
array[m]=array[n];
array[n]=temp;
}
public static void main(String[] args) {
int[] arr={4,2,2,9,1,5,8,7,6,3,0};
System.out.println("原数组为:"+ Arrays.toString(arr));
partition(arr,0,arr.length-1,6);
System.out.print("6之前比6小,之后比6大,partition之后的数组为");
System.out.println(Arrays.toString(arr));
}
}
运行结果:
2.快速排序
根据荷兰国旗问题,变形,让num=arr[arr.length-1],相当于R= arr.length-2;
经典快排缺点:如[1,2,3,4,5,6,7…N],快排就成了O(N2);
改进:随机快排:即是选择num = arr[]中随机的元素。时间复杂度O(N*logN);额外空间复杂度O(logN);【最常用的】
2.1经典快排:
public static void quickSort(int[] arr, int L, int R) {
if (L<R) {
int[] p = partition(arr, L, R);
quickSort(arr, L, p[0]-1);
quickSort(arr, p[1], R);
}
}
public static int[] partition(int[] arr, int L, int R) {
int less = L-1;
int more = R;
int cur = L;
while (cur<more) {
if (arr[cur]<arr[R]) {
swap(arr,++less,cur++);
}else if (arr[cur]>arr[R]) {
swap(arr,--more,cur);
}else {
cur++;
}
}
swap(arr, more, R);
return new int[] {less+1, more};
}
private static void swap(int[] arr, int i, int j) {
// TODO Auto-generated method stub
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
2.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),额外空间复杂度O(1)
如果只是建立堆的过程,时间复杂度为O(N);
优先级队列结构就是堆结构。
堆: 完全二叉树。每一层从左到右依次补齐,满二叉树属于完全二叉树。
当一个数组满足左:2i+1,右:2i+2,父:(i-1)/2就是完全二叉树结构。
大根堆:任何子树的最大值都是子树的根节点,
小根堆:任何子树的最小值都是子树的根节点。
用处:比如实时求所输入数据的中位数。
大根堆的建立:有任何一个子树变成大根堆。复杂度=log1+ log2+… +logN=O(N)
import java.util.Arrays;
public class HeapSort {
public static void heapSort(int[] array){
if(array==null||array.length<2){
return;
}
//建立大根堆
for (int i=0;i<array.length;i++){
heapInsert(array,i);
}
int heapSize=array.length;
swap(array,0,--heapSize);
while(heapSize>0){
heapify(array,0,heapSize);//调整大根堆
swap(array,0,--heapSize);//将大根堆的根和最后一个元素交换,然后size缩小1个
}
}
public static void heapInsert(int[] array,int index){
while(array[index]>array[(index-1)/2]){
swap(array,index,(index-1)/2);
index=(index-1)/2;
}
}
public static void swap(int[] array,int m,int n){
int temp=array[m];
array[m]=array[n];
array[n]=temp;
}
public static void heapify(int[] array,int index,int heapSize){
//找到left
int left=index*2+1;
//进行循环
while (left<heapSize){
//确定left和right中最大的位置
int largest=left+1>heapSize&&array[left+1]>array[left] ? left+1:left;
//确定孩子结点与父节点中最大的位置
largest=array[largest]>array[index] ? largest:index;
//如果最大位置和父节点位置相同,则跳出循环
if(largest==index){
break;
}
//否则交换最大值和父节点的值,变量更新
swap(array,index,largest);
index=largest;
left=2*index+1;
}
}
public static void main(String[] args) {
int[] arr={4,2,2,9,1,5,8,7,6,3,0};
System.out.println("原数组为:"+ Arrays.toString(arr));
heapSort(arr);
System.out.print("堆排序后的数组为");
System.out.println(Arrays.toString(arr));
}
}
4. 排序的稳定性:
稳定性:排序外,相同元素保持出现的先后顺序。
复杂度是O(N2):
- 冒泡排序:当遇到相同数时,该数不交换,将后面的数往下沉。可以稳定;
- 插入排序:当遇到相同数时,该数不交换;可以稳定;
- 选择排序:做不到稳定性。因为你要从后面的所有数中找到最小的,然后将前面的某一个a与该最值交换,如果有多个a存在,那么,a的先后顺序将无法保证。故做不到。
复杂度是O(N*logN);
- 归并排序:merge时,当相同时先拷贝左边(小区域)的数;可以稳定
- 快排:做不到稳定性;
- 堆排:做不到稳定性。在建大根堆的时候,就都已经不能保证稳定性了。
工程中的排序:
- 基础类型:快排
- 自定义类型:归并排序(稳定性)
- 如果数组长度较短:不管什么类型,都用插排(时间复杂度O(N^2)劣势显示不出来,反而额外空间复杂度O(1)较快)。