排序算法
2021-09-04 22:36 JMU软件1911王杰 阅读(53) 评论(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);
}
利用异或运算的交换律和结合律
例题: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));
}
因为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公式的递归:子模式相等
归并排序
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公式计算
所以时间复杂度: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
2.0
快排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)
完全二叉树:
大根堆
每一个节点的头节点的值比子节点的数大
从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
- 底层是数组,如果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;
}
}