一:快速排序(速度快)
核心思想:选择一个基准元素(pivot),将数组分为两部分,左边小于 pivot,右边大于 pivot,然后递归处理子数组。平均时间复杂度为 O(n log n),但在最坏情况下会退化到 O(n²)
下列代码中的基准元素为(arr[R]);
1.荷兰国旗问题
1.递归方法
public class quickSort {
public static void main(String[] args) {
int []arr={5,1,2,3,4,5,4,8,9,7,11};
quickSort1(arr);
for(int i:arr)
{
System.out.print(i+" ");
}
}
public static void quickSort1(int [] arr)
{
if(arr==null||arr.length<2)
return;
prosses(arr,0,arr.length-1);
}
public static void prosses(int[] arr,int L,int R)
{
if(L>=R)//递归出口
return;
swap(arr,L+(int)(Math.random()*(R-L+1)),R);
int[] equalArea=netherlandsFlag(arr,L,R);
prosses(arr,L,equalArea[0]-1);//左边
prosses(arr,equalArea[1]+1,R);//右边
}
public static void swap(int [] arr,int a,int b)//实参应该为索引
{
int temp=arr[a];
arr[a]=arr[b];
arr[b]=temp;
}
public static int [] netherlandsFlag(int [] arr,int L,int R)//将数组划分为3块区域的函数==方法
{
if(L>R)
return new int []{-1,-1};
if(L==R)
return new int[]{L,R};
int less=L-1;//小于区域的右边界
int more=R;//大于区域的左边界
int index=L;//当前检查的索引
while(index<more)
{
if(arr[index]==arr[R])
index++;
else if(arr[index]<arr[R])
swap(arr,++less,index++);
else
swap(arr,--more,index);
}
swap(arr,more,R);
return new int[] {less+1,more};
}
}
2.非递归方法(利用栈)
public class quickSort {
public static void main(String[] args) {
int []arr={5,1,2,3,4,5,4,8,9,7,11};
quickSort2(arr);
for(int i:arr)
{
System.out.print(i+" ");
}
}
//用栈来代替递归
public static class Op{
public int l;
public int r;
public Op(int left,int right){
l=left;
r=right;
}
}
public static void quickSort2(int[] arr)
{
if(arr==null||arr.length<2)
return;
int N=arr.length;
swap(arr,(int)(Math.random()*N),N-1);
int[] equalArea=netherlandsFlag(arr,0,N-1);
int el=equalArea[0]-1;
int er=equalArea[1]+1;
Stack<Op> stack=new Stack<>();
stack.push(new Op(0,el));//将范围入栈
stack.push(new Op(er,N-1));
while(!stack.isEmpty())
{
//先将站顶的出栈再对其分割出的范围入栈
Op op=stack.pop();
if(op.l<op.r)
{
swap(arr,op.l+(int)(Math.random()*(op.r-op.l+1)),op.r-1);
equalArea=netherlandsFlag(arr,op.l,op.r-1);
el=equalArea[0]-1;
er=equalArea[1]+1;
stack.push(new Op(0,el));//将范围入栈
stack.push(new Op(er,op.r-1));
}
}
}
public static void swap(int [] arr,int a,int b)//实参应该为索引
{
int temp=arr[a];
arr[a]=arr[b];
arr[b]=temp;
}
public static int [] netherlandsFlag(int [] arr,int L,int R)
{
if(L>R)
return new int []{-1,-1};
if(L==R)
return new int[]{L,R};
int less=L-1;//小于区域的右边界
int more=R;//大于区域的左边界
int index=L;//当前检查的索引
while(index<more)
{
if(arr[index]==arr[R])
index++;
else if(arr[index]<arr[R])
swap(arr,++less,index++);
else
swap(arr,--more,index);
}
swap(arr,more,R);
return new int[] {less+1,more};
}
}
二:归并排序(具有稳定性)
核心思想:将数组分成两半,分别排序后再合并。时间复杂度为 O(n log n)
1.递归方法
public class mergeSort {
public static void mergeSort1(int [] arr){//递归并归排序
if(arr==null||arr.length<2){
return;
}
process(arr,0,arr.length-1);
}
public static void process(int [] arr, int L,int R)
{
int mid=L+((R-L)>>1);
if(L==R)
return;
process(arr,L,mid);
process(arr,mid+1,R);
merge(arr,L,mid,R);
}
public static void merge(int [] arr,int L,int M,int R){
int p1=L;
int p2=M+1;//双指针
int cnt=0;
int [] help=new int[R-L+1];
while(p1<=M&&p2<=R)
{
help[cnt++] = arr[p1]<=arr[p2]?arr[p1++]:arr[p2++];
}
while(p1<=M)//p2越界
{
help[cnt++]=arr[p1++];
}
while(p2<=R)//p1越界
{
help[cnt++]=arr[p2++];
}
for(int i=0;i<help.length;i++)
{
arr[L+i]=help[i];
}
}
public static void main(String[] args) {
int [] arr={1,9,5,3,7,4,2,5,7,8};
mergeSort1(arr);
for(int i=0;i<arr.length;i++)
{
System.out.print(arr[i]+" ");
}
}
}
2.非递归方法
public class mergeSort {
public static void mergeSort2(int [] arr){//非递归并归排序
if(arr==null||arr.length<2){
return;
}
int N=arr.length;
int mergeSize=1;//步长
while(mergeSize<N)
{
int L=0;//左组第一个元素索引
while(L<N)//将此步长都merge完
{
int M=L+mergeSize-1;//左组最后一个元素索引
if(M>N)
break;
int R=Math.min(N-1,M+mergeSize);//右组最后一个元素索引
merge(arr,L,M,R);
L=R+1;
}
if(mergeSize>N/2)
break;
mergeSize<<=1;
}
}
public static void merge(int [] arr,int L,int M,int R){
int p1=L;
int p2=M+1;//双指针
int cnt=0;
int [] help=new int[R-L+1];
while(p1<=M&&p2<=R)
{
help[cnt++] = arr[p1]<=arr[p2]?arr[p1++]:arr[p2++];
}
while(p1<=M)//p2越界
{
help[cnt++]=arr[p1++];
}
while(p2<=R)//p1越界
{
help[cnt++]=arr[p2++];
}
for(int i=0;i<help.length;i++)
{
arr[L+i]=help[i];
}
}
public static void main(String[] args) {
int [] arr={1,9,5,3,7,4,2,5,7,8};
mergeSort2(arr);
for(int i=0;i<arr.length;i++)
{
System.out.print(arr[i]+" ");
}
}
}
归并排序面试题:
1.小和问题(在一个数组中,每个数左边比它小的数的和,称为该数的小和。求数组中所有数的小和的总和)
public static int smallSum(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
return process(arr, 0, arr.length - 1);
}
public static int process(int[] arr, int L, int R) {
if (L == R) {
return 0;
}
int mid = L + ((R - L) >> 1);
return process(arr, L, mid) + process(arr, mid + 1, R) + merge(arr, L, mid, R);
}
public static int merge(int[] arr, int L, int M, int R) {
int[] help = new int[R - L + 1];
int i = 0;
int p1 = L;
int p2 = M + 1;
int res = 0;
while (p1 <= M && p2 <= R) {
res += arr[p1] < arr[p2] ? (R - p2 + 1) * arr[p1] : 0;
help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= M) {
help[i++] = arr[p1++];
}
while (p2 <= R) {
help[i++] = arr[p2++];
}
for (i = 0; i < help.length; i++) {
arr[L + i] = help[i];
}
return res;
}
public static void main(String[] args) {
int[] arr = {4,2,1,3,0,6,3,9};
System.out.println(smallSum(arr));
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
2.Bigger Than Right Twice:(在一个数组中每个数num的右边有多少个数乘以2任然小于num,返回总个数)
public static int BiggerThanRightTwice(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
return process(arr, 0, arr.length - 1);
}
public static int process(int[] arr, int L, int R) {
if (L == R) {
return 0;
}
int mid = L + ((R - L) >> 1);
return process(arr, L, mid) + process(arr, mid + 1, R) + merge(arr, L, mid, R);
}
public static int merge(int[] arr, int L, int M, int R) {
int ans=0;
int windowR=M+1;//索引
for(int i=L;i<=M;i++)
{
while(windowR<=R&&arr[i]>(arr[windowR]*2))//不越界并且符合题目条件
{
windowR++;
}
ans+=windowR-M-1;
}
int[] help = new int[R - L + 1];
int i = 0;
int p1 = L;
int p2 = M + 1;
int res = 0;
while (p1 <= M && p2 <= R) {
res += arr[p1] < arr[p2] ? (R - p2 + 1) * arr[p1] : 0;
help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= M) {
help[i++] = arr[p1++];
}
while (p2 <= R) {
help[i++] = arr[p2++];
}
for (i = 0; i < help.length; i++) {
arr[L + i] = help[i];
}
return ans;
}
public static void main(String[] args) {
int[] arr = {4,2,8,3,2,6,3,1};
System.out.println(BiggerThanRightTwice(arr));
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
三:冒泡排序
核心思想:通过多次遍历数组,比较相邻元素并交换顺序错误的元素
时间复杂度:
最坏 O(n²)
最好:O(n)(数组已有序时优化后提前退出)
public class BubbleSort {
public static void bubbleSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int i = 0; i < arr.length - 1; i++) { // 外层循环控制轮次
for (int j = 0; j < arr.length - 1 - i; j++) { // 内层循环比较相邻元素
if (arr[j] > arr[j + 1]) {
swap(arr, j, j + 1);
}
}
}
}
public static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static void main(String[] args) {
int[] arr = {5, 2, 9, 1, 5, 6};
bubbleSort(arr);
System.out.println(Arrays.toString(arr));
}
}
四:选择排序
核心思想:每次从未排序部分选择最小(或最大)的元素,放到已排序部分的末尾
时间复杂度: O(n²)
public class SelectionSort {
public static void selectionSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int i = 0; i < arr.length - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
swap(arr, i, minIndex);
}
}
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static void main(String[] args) {
int[] arr = {64, 25, 12, 22, 11};
selectionSort(arr);
System.out.println(Arrays.toString(arr));
}
}
五:插入排序
核心思想:将未排序部分的元素逐个插入到已排序部分的正确位置
时间复杂度为: O(n²)
public class InsertionSort {
public static void insertionSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int i = 1; i < arr.length; i++) { // 从第二个元素开始
int current = arr[i]; // 当前待插入的元素
int j = i - 1; // 已排序部分的最后一个索引
// 从后往前比较,找到合适的插入位置
while (j >= 0 && arr[j] > current) {
arr[j + 1] = arr[j]; // 后移元素
j--;
}
arr[j + 1] = current; // 插入到正确位置
}
}
public static void main(String[] args) {
int[] arr = {12, 11, 13, 5, 6};
insertionSort(arr);
System.out.println(Arrays.toString(arr));
}
}
六:堆排序(空间小O(1))
时间复杂度为: O(N*logN)
下述代码为大根堆
public class HeapSort {
public static void main(String[] args) {
int[] arr={2,5,4,13,8,0,6,5,4};
heapSort(arr);
for(int i:arr){
System.out.print(i+" ");
}
}
public static void heapSort(int[] arr)
{
if(arr==null||arr.length<2)
return;
//O(N)建堆
/*for(int i=arr.length-1;i>=0;--i)//从二叉树的底层开始遍历
{
heapify(arr,i,arr.length);
}*/
//O(N*logN)建堆
for(int i=0;i<arr.length;i++)
{
heapInsert(arr,i);
}
//此时二叉树已经符合大根堆了
int heapSize=arr.length;
swap(arr,0,--heapSize);//将最大的放到最后,并从逻辑上踢出二叉树
while(heapSize>0)
{
heapify(arr,0,heapSize);//由于上一步将二叉树中最后一个节点替换到头节点,
//所以需要将其下沉使得二叉树继续符合大根堆
swap(arr,0,--heapSize);
}
}
//若传入的节点大于其父节点则上浮(为了保证二叉树为大根堆)
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 heapSize)//O(logN)
{
int left=index*2+1;//左儿子节点
while(left<heapSize)//左儿子存在
{
//选择较大的儿子赋值给largest
int largest= left+1<heapSize && arr[left+1]>arr[left]?left+1:left;
//父亲和较大的孩子谁大就把谁赋值给largest
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 a,int b)
{
int temp=arr[a];
arr[a]=arr[b];
arr[b]=temp;
}
}
1.建堆:
将无序数组调整成大根堆或小根堆
时间复杂度:O(n)(从最后一个非叶子节点开始调整)
2.排序:
每次取出堆顶元素与堆的最后一个元素交换并调整剩余部分使其重新满足堆的性质。
时间复杂度:O(n log n)
堆排序面试题:
1.最大线段排序问题(给出若干线段,请返回线段重合最多的数量)
public class HeapSort {
public static int maxCover(int[][] m){
Line[] lines=new Line[m.length];
for(int i=0;i<m.length;i++)
{
lines[i]=new Line(m[i][1],m[i][0]);
}
Arrays.sort(lines,new Startcompare());
PriorityQueue<Integer> heap = new PriorityQueue<>();//构造优先队列小根堆
int max=0;
for(int i=0;i<lines.length;i++)
{
while(!heap.isEmpty()&&heap.peek()<=lines[i].start)
{
heap.poll();//将顶推出
}
max=Math.max(max,heap.size());
}
return max;
}
//比较器
public static class Startcompare implements Comparator<Line> {
@Override
public int compare(Line o1, Line o2) {
return o1.start-o2.start;
}
}
//创建线段
public static class Line{
public int start;
public int end;
public Line(int a,int b){
start=a;
end=b;
}
}
八:桶排序(不基于比较的排序)
计数排序 O(n)
要求:
1.样本是整数
2.范围较窄
原理:count数组的下标就是对应数字,数组内的值就是这个值(当前下标)的个数
代码演示
public static void countingSort(int[] arr) {
if (arr == null || arr.length < 2) return;
int max = arr[0];
int min = arr[0];
for (int num : arr) {
if (num > max) max = num;
if (num < min) min = num;
}
int range = max - min + 1;//数组范围
int[] count = new int[range];
for (int num : arr) {
count[num - min]++;
}
int[] output = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
int num = arr[i];
output[count[num - min] - 1] = num;
count[num - min]--;
}
System.arraycopy(output, 0, arr, 0, arr.length);
}
基数排序O(n)
要求:
1.样本是10进制的数
原理:按数字的每一位(从低位到高位)依次排序
代码演示
public class RadixSort1 {
public static void radixSort(int[] arr){
if(arr==null||arr.length<2)
return;
radixsort(arr,0,arr.length-1,maxbit(arr));
}
//求数组中最大值是几位数
public static int maxbit(int[] arr){
int max=(int)1e9;
for(int i=0;i<arr.length;i++)
max=Math.max(max,arr[i]);
int res=0;
while(max!=0)
{
max/=10;
res++;
}
return res;
}
public static void radixsort(int[] arr,int L,int R,int digit){
int radix=10;//count数组的长度
int[] help=new int[R-L+1];
for(int d=0;d<digit;d++)//外循环位数
{
int[] count=new int[radix];
for(int i=L;i<R;i++)
{
int j=getDigit(arr[i],d);
count[j]++;
}
for(int i=0;i<radix;i++)//前缀和
{
count[i]=count[i]+count[i-1];
}
for(int i=R;i>=L;i--)//反向遍历填充help数组
{
int j=getDigit(arr[i],d);
help[count[j]-1]=arr[i];
count[j]--;
}
}
for(int i=L,j=0;i<=R;i++,j++)
{
arr[i]=help[j];
}
}
public static int getDigit(int x, int d) {//这个数d位数的值
return ((x / ((int) Math.pow(10, d - 1))) % 10);
}
}
排序算法总结
1.不基于比较的排序,对样本数据有严格要求,不易改写
2.基于比较的排序,只要规定好两个样本怎么比大小就可以直接复用
3.基于比较的排序,时间复杂度的极限是O(NIOgN)
4)时间复杂度O(N*IOgN)、额外空间复杂度低于O(N)、且稳定的基于比较的排序是不存在的。
5)为了绝对的速度选快排、为了省空间选堆排、为了稳定性选归并