认识复杂度与排序算法笔记

1、时间复杂度

在这里插入图片描述

2、选择排序

每一次循环把最小值放到对应的位置上

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

时间复杂度 O(N²) 空间复杂度O(1)

3、冒泡排序

两两对比,把大的往后移,每一趟循环下来把大的放在了后面

pubic static void bubbleSort(int[] arr) {
	if (arr == null || arr.length < 2) {
		return;
	}
	for (int e = arr.length - 1; e > 0; e--) {
		for (int i = 0; i < e; i++) {
			if (arr[i] > arr[i+1]) {
				swap(arr,i,i+1);
			}
		}		
	}
}

时间复杂度 O(N²) 空间复杂度O(1)

4、异或运算

定义:相同为0,不同为1,还可以理解为无进位相加
位运算比算数运算快多了
在这里插入图片描述

如果要用异或交换两个数,那么这两个数在内存中的位置一定不能相同,无论这两个值相不相等,如果相等,就会把数变成0了。

例题1:一个数组中,只有一种数出现了奇数次,其他数出现了偶数次,找到出现奇数次的数,要求时间复杂度O(N),空间复杂度O(1)

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

例题2:有两种数出现了奇数次,其他出现了偶数次,怎样找到这两种数
思路:
在这里插入图片描述

public static void printOddTimesNum2(int[] arr) {
	int eor = 0;
	for (int curNum : arr) {
		eor ^= curNum;
	}
	// eor = a ^ b;
	// eor != 0
	// eor必然有一个位置上是1
	int rightOne = eor & (~eor + 1); // 提取出最后边的1
	int onlyOne = 0;
	for (int cur : arr) {
		if ((cur & rightOne) == 0) { // (cur & rightOne) == 1
			onlyOne ^= cur;
		}
	}
	System.out.println(onlyOne + " " + (eor ^ onlyOne));
}

5、插入排序

做到0 ~ 0有序,0 ~ 1有序,0 ~ 2有序,0 ~ 3有序 … 0 ~ N有序
插入排序与数据状况有关系

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

时间复杂度 O(N²) 空间复杂度O(1)

6、二分法

在这里插入图片描述

例1:有序数组中,找某个数的存在

例2:在一个有序数组中,找>=某个数最左侧的位置
在这里插入图片描述
例3:局部最小值问题
在这里插入图片描述

7、剖析递归行为和递归行为时间复杂度的估算

在这里插入图片描述

8、归并排序

先让左侧排好序,再让右侧排好序,最后再让整体有序(新开辟一个空间对左右侧进行比较)

public static void mergeSort(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) {
	if (L == R) {
		return;
	}
	int mid = L + ((R - L) >> 1);
	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[] 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 (i = 0; i < help.length; i++) {
		arr[L + i] = help[i];
	}
}

根据maser公式:T(N) = 2 * O(N/2) + O(N)
时间复杂度O(N*㏒N)空间复杂度O(N)
在这里插入图片描述

  1. 小和问题代码实现:
    相等时候一定要先拷贝右组的数据
public static int smallSort(int[] arr) {
	if (arr == null || arr.length < 2) {
		return;
	}
	return process(arr,0,arr.length-1);
}
// arr[L..R]既要排好序,也要求小和
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;
}
  1. 逆序对问题
    力扣原题
public int reversePairs(int[] nums) {
        if (nums == null || nums.length < 2) {
            return 0;
        }
        return process(nums,0,nums.length-1);
    }

    public int process(int[] nums,int L,int R) {
        if (L == R) {
            return 0;
        }
        int mid = L + ((R - L) >> 1);
        return process(nums,L,mid)
                + process(nums,mid + 1,R)
                + merge(nums,L,mid,R);
    }

    public int merge(int[] nums,int L,int M,int R) {
        int[] help = new int[R - L + 1];
        int p1 = L;
        int p2 = M + 1;
        int i = 0;
        int res = 0;
        while (p1 <= M && p2 <= R) {
            res += nums[p1] > nums[p2] ? (M - p1 + 1) : 0;
            help[i++] = nums[p1] <= nums[p2] ? nums[p1++] : nums[p2++];
        }
        while (p1 <= M) {
            help[i++] = nums[p1++];
        }
        while(p2 <= R) {
            help[i++] = nums[p2++];
        }
        for (int j = 0; j < help.length; j++) {
            nums[L + j] = help[j];
        }
        return res;
    }

9、快速排序

先看荷兰国旗问题

  1. 给定一个数组arr,和一个数num,请把小于等于num的数放在数组的左边,大于num的数放在数组的右边。要求额外空间复杂度O(1),时间复杂度O(N)
    在这里插入图片描述

  2. 给定一个数组arr,和一个数num,请把小于num的数放在数组的左边,等于num的数放在数组的中间,大于num的数放在数组的右边。要求额外空间复杂度O(1),时间复杂度O(N)
    在这里插入图片描述

快排1.0

在这里插入图片描述

快排2.0,2.0版本比1.0版本要稍快一点

在这里插入图片描述
但是不管是1.0还是2.0版本,时间复杂度都是O(N^2),原因是划分值打得很偏,可能只有左侧或者只有右侧部分,这时候时间复杂度就会退化

快排3.0版本,随机算一个数作为划分值,好情况和坏情况就是一个概率时间

在这里插入图片描述

我习惯的写法

public void quickSort(int []nums,int begin,int end) {
        if (begin < end) {
            int index = getIndex(nums,begin,end);
            quickSort(nums,begin,index-1);
            quickSort(nums,index+1,end);
        }
    }

private static int getIndex(int[] nums,int low,int high) {
	    int temp = nums[low];
	    while (low < high) {
	        while (low < high && nums[high] > temp) {
	            high--;
	        }
	        nums[low] = nums[high];
	        while (low < high && nums[low] <= temp) {
	            low++;
	        }
	        nums[high] = nums[low];
	    }
	    nums[low] = temp;
	    return low;
	}

时间复杂度O(N*logN),空间复杂度O(N)

10、堆

在这里插入图片描述
在这里插入图片描述

大根堆

在这里插入图片描述

调整大根堆

// 某个数现在处在index位置,往上继续移动
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;
	}
}

返回并删除最大值(根节点),把最后一个节点拷贝到头结点,然后从头结点开始,跟它的左右孩子的最大值进行比较

// 某个数现在处在index位置,往下继续移动
public static void heapify(int[] arr,int index,int heapSize) {
	int left = 2 * index +1;
	while (left < heapSize) {
		int largest = left + 1 < heapSize && arr[left] < arr[left + 1] 
						? left + 1 : left;
		largest = arr[index] > arr[largest] ? index : lardest;
		if (largest == index) {
			break;
		}
		swap(arr,largest,index);
		index = largest;
		left = 2 * index +1;;
	}
}

堆排序,先让数组整体形成一个大根堆,然后让堆的根节点与最后一个节点进行交换,heapSize–,这样就把最大值放在了数组的最后面,依次执行这个过程,当heapSize==0,最后数组有序。

时间复杂度 O(N*logN),空间复杂度O(1)

public static void heapSort(int[] arr) {
	if (arr == null || arr.length < 2) {
		return;
	}
	for (int i = 0; i < arr.length; i++) { // O(N)
		heapInsert(arr,i); // O(logN)
	}
	swap(arr,0,--heapSize);
	while (heapSize > 0) { // O(N)
		heapify(arr,0,heapSize); // O(logN)
		swap(arr,0,--heapSize); // O(1)
	}
}

例题:
在这里插入图片描述
在这里插入图片描述

public void sortedArrDistanceLessK(int[] arr,int k) {
	// 默认小根堆
	PriorityQueue<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++) {
		heap.add(arr[index]);
		arr[i] = heap.pop();
	}
	while (!heap.isEmpty()) {
		arr[i++] = heap.pop();
	}
}

11、比较器的使用

在这里插入图片描述
在这里插入图片描述

12、基数排序(不基于比较的排序)

在这里插入图片描述
先按个位数的数字进桶,然后从左往右依次倒出,然后再按十位数的数字进桶,依此类推,最后一定会排好序。

public static void radixSort(int[] arr) {
	if (arr == null || arr.length < 2) {
		return;
	}
	radixSort(arr,0,arr.length - 1,maxbits(arr));
}
public static int maxbits(int[] arr) {
	int max = Integer.MIN_VALUE;
	for (int i = 0; i < arr.length; i++) {
		max = Math.max(max,arr[i]);
	}
	int res = 0;
	while (max != 0) {
		res++;
		max /= 10;
	}
	return res;
}
public static void radixSort(int[] arr,int L,int R,int digit) {
	final int radix = 10;
	int i = 0, j = 0;
	// 有多少个数准备多少个辅助空间
	int[] bucket = new int[R - L + 1];
	for (int d = 1; d <= digit; d++) { // 有多少位就进出几次
		// 10个空间
		// count[0] 当前位(d位)是0的数字有多少个
		// count[1] 当前位(d位)是(0和1)的数字有多少个
		// count[2] 当前位(d位)是(0、1和2)的数字有多少个
		// count[1] 当前位(d位)是(0~i)的数字有多少个
		int[] count = new int[radix]; // count[0...9]
		for (i = L; i <= R; i++) {
			j = getDigit(arr[i],d);
			count[j]++;
		}
		for (i = 1; i < radix; i++) {
			count[i] = count[i] + count[i - 1];
		}
		for (i = R; i >= L; i--) {
			j = getDigit(arr[i],d);
			bucket[count[j] - 1] = arr[i];
			count[j]--;
		}
		for (i = L, j = 0; i <= R; i++,j++) {
			arr[i] = bucket[j];
		}
	}
}
public static int getDigit(int x,int d) {
	return ((x / ((int) Math.pow(10,d - 1))) % 10);
}

13、排序算法的稳定性及其汇总

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

(1)可以根据样本数量充分利用排序算法O(N*logN)和O(N^2)的优势。能用快排的时候就用快排,但是不稳定。

(2)稳定性的考虑
Arrays.sort( )在基础类型的时候使用快速排序,非基础类型使用归并排序,因为非基础类型它会考虑排序后的稳定性问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值