代码改变世界

排序算法

2021-09-04 22:36  JMU软件1911王杰  阅读(51)  评论(0编辑  收藏  举报

算法笔记

选择排序

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-1;j++){
            minIndex=arr[j]<arr[minIndex]?j:minIndex;
        }
        swap(arr,i,minIndex);
    }
    
}

空间复杂度O(1) 时间复杂度O(N^2)

冒泡排序

public static void bubbleSort(int[] arr){
     //丢弃杂数据
    if(arr==null||arr.length<2){
        return;
    }
	for(int i=arr.length-1;i>0;i--){
        for(int j=0;j<i;j++){
            if(arr[j]>arr[j+1]){
                swap(arr,j,j+1)
            }
        }
    }
}

空间复杂度O(1) 时间复杂度O(N^2)

异或运算

  • 可以理解为无进位相加

  • 0^N=N N^N=0

  • 满足交换律和结合律 ab=ba (ab)c=a(bc)

  • 应用:可以实现两个值的交换

    • a=a^b; a=a^b,b=b
    • b=a^b; a=ab,b=ab^b=a
    • a=a^b; a=aba=b,b=a
例题:1

寻找一个数组中出现奇数次数的数(只有一个数出现奇数次)

	public static void printOddTimesNum1(int[] arr) {
		int eO = 0;
		for (int cur : arr) {
			eO ^= cur;
		}
		System.out.println(eO);
	}

利用异或运算的交换律和结合律

image-20210901232127329

例题:2

寻找一个数组中出现奇数次数的数(有两个数出现奇数次)

public static void printOddTimesNum2(int[] arr) {
		int eO = 0, eOhasOne = 0;
    	//此时e0=a^b
		for (int curNum : arr) {
			eO ^= curNum;
		}
    	//找到e0最右侧非0数
		int rightOne = eO & (~eO + 1);
		for (int cur : arr) {
			if ((cur & rightOne) != 0) {
				eOhasOne ^= cur;
			}
		}
		System.out.println(eOhasOne + " " + (eO ^ eOhasOne));
	}

image-20210901234441349

因为a!=b,所以a与b的二进制数的某一位里面一个是1一个是0

rightOne就找到了这个为1的位,然后只异或这个位为1的数,最后的结果一定是a或者b

选择排序

public static void insertionSort(int[] arr){
    if(arr==null&&arr.length==2){
        return;
    }
    for(int i=0;i<arr.length-1;i++){
        for(int j=i-1;j>=0&&arr[j]>arr[j+1];j--){
            swap(arr,j,j+1);
        }
    }
}

二分查找

public static boolean exist(int[] sortedArr, int num) {
    	if (sortedArr == null || sortedArr.length == 0) {
			return false;
		}
    	int L=0,R=sortedArr.length-1,mid=0;
    	while(L<R){
            mid=L+((R-L)>>1);
            if(sortedArr[mid]==num){
                return true;
            }
            else if(sortedArr[mid]>num){
                R=mid-1;
            }else{
                L=mid+1;
            }
        }
    return sortedArr[L]==num;
}

递归求最大值

public static int process(int[] arr,int L,int R){
    if(L==R){
        return arr[L];
    }
    int mid = L + ((R-L)>>1);
    int leftMax=process(arr,L,mid);
    int rightMax=process(arr,mid,R);
    return Math.max(leftMax,rightMax);
}

时间复杂度:O(N)

满足master公式的递归时间复杂度

符合master公式的递归:子模式相等

image-20210902220946105

归并排序

public static void mergeSort(int[] arr){
    if(arr==null||arr.length<2){
        return;
    }
    mergeSort(arr,0,arr.length-1);
}
public static void mergeSort(int[] arr,int l,int r){
    if(l==r){
        return;
    }
    int mid=l+((r-l)>>1);
    mergSort(arr,l,mid);
    mergSort(arr,mid+1,r);
    merge(arr,l,mid,r);
  
}
public static void 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;
    while(p1<=m&&p2<=r){
        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(int i=0;i<help.length;i++){
        arr[l+i]=help[i];
    }
}

用master公式计算

image-20210902230855674

image-20210902230911192

所以时间复杂度:O(NlogN)空间复杂度O(N)

选择,冒泡,插入排序不如归并排序的原因,浪费比较行为

归并排序拓展

小和问题和逆序对问题

小和问题 :

在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组 的小和。求一个数组 的小和。 例子:[1,3,4,2,5] 1左边比1小的数,没有; 3左边比3小的数,1; 4左 边比4小的数,1、3; 2左边比2小的数,1; 5左边比5小的数,1、3、4、 2; 所以小和为1+1+3+1+1+3+4+2=16

public static int smallSum(int[] arr) {
		if (arr == null || arr.length < 2) {
			return 0;
		}
		return mergeSort(arr, 0, arr.length - 1);
	}
public static int mergeSort(int[] arr,int l,int r){
    if(r==l){
        return 0;
    }
    int mid=l((r-l)>>1);
    return mergeSort(arr,r,mid)
        	+mergeSort(arr,mid+1,r)
        	+merge(arr,l,mid,r);
}
public static int merge(int[] arr,int l,int r,int m){
    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(int i=0;i<help.length-1;;i++){
        arr[l+i]=help[i];
    }
    return res;
    
}

逆序对问题 在一个数组中,左边的数如果比右边的数大,则这两个数 构成一个逆序对,请打印所有逆序 对

快排1.0

image-20210903003443048

2.0

image-20210903003543471

快排3.0 加上随机

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=partiton(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};
    }
}

时间复杂度:O(NlogN)空间复杂度O(logN)

完全二叉树:

image-20210903215822997

大根堆

每一个节点的头节点的值比子节点的数大

从index位置开始调整大根堆

往下调整

public static void heapify(int[] arr,int index,int heapSize){
    int left=index*2+1;
    while(left < heapSize){
        int largest = left + 1 < heapSzie && 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 heapInsert(int[] arr,int index){
    while(arr[index] > arr[(index - 1) / 2]){
        swap(arr,index,(index-1)/2);
        index=(index-1)/2;
    }
}

完全二叉树的高度

如果有N个节点则高度:logN

堆排序

堆排序 1,先让整个数组都变成大根堆结构,建立堆的过程: 1)从上到下的方法,时间复杂度为O(NlogN) 2)从下到上的方法,时间复杂度为O(N) 2,把堆的最大值和堆末尾的值交换,然后减少堆的大小之后,再去调 整堆,一直周而复始,时间复杂度为O(NlogN) 3,堆的大小减小成0之后,排序完成

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 heapSize = arr.length;
    swap(arr,0,--heapSize);
    while(heapSIze>0){
        heapiyf(arr,0,heapSize);
        swap(arr,0,--heapSize);
    }
}

堆排序扩展题目

已知一个几乎有序的数组,几乎有序是指,如果把数组排好顺序的话,每个元 素移动的距离可以不超过k,并且k相对于数组来说比较小。请选择一个合适的 排序算法针对这个数据进行排序

系统提供的小根堆:

PriortyQueue heap =new PriorityQueue<>();

  • 底层是数组,如果heapsize大于数组长度的时候,arr.length*2

扩容的代价是O(logN)水平

  • 如果使用系统的堆结构,不支持修改内层堆结构,比如修改某个中间的位置然后让它继续成为堆,只能进行插入和排除操作
  • 手写堆的情况:需要中间调整堆。
public void sortedArrDistanceLessK(int[] arr,int k){
    PriortyQueue<Integer> heap =new PriorityQueue<>();
    int index = 0;
    for(;index <= Math.min(arr.length,k);index++){
        heap.add(arr[index]);
    }
    int i=0;
    for(;index<arr.length;i++,index++){
        arr[i]=heap.poll;
        heap.add[arr[index]];
    }
    while (!heap.isEmpty()){
        arr[i++]=heap.poll;
    }
}