一 排序
1.简单选择排序
思想:每次选择一个最小的元素和前面的交换
交换次数:N
比较次数:(N-1)+(N-2)+…+1=N^2/2 次比较
算法特点:
1.运行时间和初始顺序无关
2.数据移动是最少的——N次
3.最终位置确定
public class SelectSort01 {
private SelectSort01(){}//私有化构造函数(不让创建对象,直接用)
public static void selectsort(int[] a){
int i, j, min;;
for (i = 0; i <a.length ; i++) {
min = i;
for (j=i+1; j<a.length;j++){
if(a[j] < a[min]){
min = j;
}
}
swap(a, i, min);
}
}
public static void swap(int[] a,int i,int j){
int temp;
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
2.插入排序
思路:从第二个元素开始,内循环和前面的元素比较,若比前面的小,则前面元素后移,将当前元素插入到合适位置。
特点:
1.最终位置不确定
2.时间和初始顺序有关
这个是只要不一样就进行交换,直到往前逐步交互到有序
这个是先把这个破坏有序的当前元素暂存,然后和前面的比较,比他大的全部后移,然后再把它插入
这个耗时要小一点
public class Insert01 {
private Insert01(){}
public static <E extends Comparable<E>> void insert(E[] arr){
for(int i = 1;i<arr.length;i++){
if (arr[i].compareTo(arr[i-1])<0){
E t = arr[i];//后移元素暂存
int j;
for (j=i;j-1>=0&&t.compareTo(arr[j-1])<0;j--){
arr[j] = arr[j - 1];//后移
}
arr[j] = t;
}
}
}
public static <E> void swap(E[] a,int i,int j){
E temp;
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
public static void main(String[] args) {
Integer[] arr = {4, 22, 74,6,8, 9,3};
insert(arr);
for(int i:arr){
System.out.print(i+" ");
}
}
}
3.归并排序
(1)自顶向下
将大问题通过递归变成小问题
不断的找到mid,划分数组,直到成2个为一组的数组,然后进行排序。即每次把两个有序的数组合成一个有序的大数组,在不断递归回去
此处是把辅助数组作为merge方法里的局部变量,这样的话容易每次merge都去创建数组。所以可以把他作为sort里的全局变量。
public class Merge {
public static void mergeSort(int[] arr) {
mergeSort(arr, 0, arr.length - 1);
}
private static void mergeSort(int[] arr, int l, int r) {
if (r <= l) {
return;
}
int mid = l + ((r - l) >> 1);
mergeSort(arr, l, mid);
mergeSort(arr, mid + 1, r);
//优化:可以在有序情况下不进行merge从而提升效率
if (arr[mid]-arr[mid + 1] > 0) {
//此时是把[l,mid]和[mid+1,r]两个分别有序的数组合并为一个有序数组
merge(arr, l, mid, r);
}
}
private static void merge(int[] arr, int l, int mid, int r) {
//辅助数组
int[] help = new int[r - l + 1];
int i = 0;//help的索引
int p1 = l;//第一个数组的开始
int p2 = mid + 1;//第二个数组的开始
while (p1 <= mid && p2 <= r) {
help[i++] = arr[p1]-arr[p2] <= 0 ? arr[p1++] : arr[p2++];
}
while (p1 <= mid) {
help[i++] = arr[p1++];
}
while (p2 <= r) {
help[i++] = arr[p2++];
}
//回填到原数组
for (int j = 0; j < help.length; j++) {
arr[l + j] = help[j];
}
}
}
(2)自底向上
将小问题一步一步解决最终解决大问题
更适合链表的结构
其他情况自顶向下的更快
public static void sort(int[] a) {
if (a.length == 0) {
return;
}
int N = a.length;
//d表示子数组大小
for (int d = 1; d < N; d += d) {
//merge两个子数组。lo表示子数组的索引,每次更新到第三个子数组索引
for (int lo = 0; lo < N - d; lo += d + d) {
merge(a, lo, lo + d - 1, Math.min(lo + d + d - 1, N - 1));
}
}
}
private static void merge(int[] a, int l, int mid, int r) {
if (l >= r) {
return;
}
int[] help = new int[r - l + 1];
int index = 0;
int p1 = l;
int p2 = mid + 1;
while (p1 <= mid && p2 <= r) {
help[index++] = a[p1] - a[p2] <= 0 ? a[p1++] : a[p2++];
}
while (p1 <= mid) {
help[index++] = a[p1++];
}
while (p2 <= r) {
help[index++] = a[p2++];
}
for (int i = 0; i < help.length; i++) {
a[l + i] = help[i];
}
}
public static void main(String[] args) {
int[] a = {9, 64, 3, 6, 2, 5, 7, 22, 1};
sort(a);
for (int i = 0; i < a.length; i++) {
System.out.println(a[i]);
}
}
(1)归并排序的应用-1.小和问题
public class SmallAdd {
public static int getAdd(int[] arr) {
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);
}
private static int merge(int[] arr, int l, int mid, int r) {
int[] arr1 = new int[r - l + 1];
int i = 0;
int p1 = l;
int p2 = mid + 1;
int res = 0;
while (p1 <= mid && p2 <= r) {
if (arr[p1] < arr[p2]) {//相等时候是吧右边的放入新数组!!!
res += (r - p2 + 1) * arr[p1];
arr1[i++] = arr[p1++];
} else {
res += 0;//!!!
arr1[i++] = arr[p2++];
}
}
while (p1 <= mid) {
arr1[i++] = arr[p1++];
}
while (p2 <= r) {
arr1[i++] = arr[p2++];
}
for (int j = 0; j < arr1.length; j++) {
arr[l + j] = arr1[j];
}
return res;
}
public static void main(String[] args) {
int[] arr = {1,3,4,2,5};
System.out.println(getAdd(arr));
}
}
(2)归并排序的应用-2.剑指 Offer 51. 数组中的逆序对
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
示例 1:
输入: [7,5,6,4]
输出: 5
限制:
0 <= 数组长度 <= 50000
class Solution {
public int reversePairs(int[] nums) {
if(nums.length<2){
return 0;
}
int process = merge(nums, 0, nums.length - 1);
return process;
}
public static int merge(int[] arr,int l,int r){
if (l >= r) {
return 0;
}
int mid = l + ((r - l) >> 1);
return merge(arr,l,mid)+merge(arr,mid+1,r)+process(arr,l,r,mid);
}
public static int process(int[] arr, int l, int r,int mid) {
int p1 = l;
int p2 = mid + 1;
int res = 0;
int i = 0;
int[] ints = new int[r - l + 1];
//从大到小排序
while (p1 <= mid && p2 <= r) {
if (arr[p1] > arr[p2]) {
res +=(r-p2+1);
ints[i++] = arr[p1++];
} else {
res +=0;
ints[i++] = arr[p2++];
}
}
while (p1 <= mid) {
ints[i++] = arr[p1++];
}
while (p2 <= r) {
ints[i++] = arr[p2++];
}
for (int j = 0; j < ints.length; j++) {
arr[l + j] = ints[j];
}
return res;
}
}
4.快速排序
优化:
1.可以在小数组时候使用插入排序
if(low+M>= high){
插入排序
}
(1)基础版本
public class Sort {
public static void sortMain(int[]arr) {
Random random = new Random();
sort(arr,0,arr.length-1,random);
}
public static void sort(int arr[], int low, int high,Random random) {
if (low < high) {
int p = partition(arr, low, high,random);
sort(arr, low, p - 1,random);
sort(arr, p + 1, high,random);
}
}
public static int partition(int arr[], int low, int high,Random random) {
int nextInt = low + random.nextInt(high - low + 1);
int pivot = arr[nextInt];//随机位置作为中枢
arr[nextInt] = arr[low];
arr[low] = pivot;
while (low < high) {
//此处时遇到相等的就跳过,可以避免重复数据的交换。
//但是当相同元素过多时候会编程平方级别的复杂度。
//所以可以变成arr[high] > pivot,或者用三路快排
while (low < high && arr[high] >= pivot) {
high--;
}
arr[low] = arr[high];
while (low < high && arr[low] <= pivot) {
low++;
}
arr[high] = arr[low];
}
arr[low] = pivot;
return low;
}
public static void main(String[] args) {
int a[] = {5, 23, 7, 8, 1, 65, 68, 90, 34, 3, 0};
sortMain(a);
for (int i = 0; i < a.length; i++) {
System.out.print(a[i]+" ");
}
}
}
(2)三路快排
每次按照pivot划分成小于的,等于的,大于的三个部分
public class QuickSort {
//三路排序实现快排
public static void sort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
quickSort(arr, 0, arr.length - 1);
}
private static void quickSort(int[] arr, int l, int r) {
if (l < r) {
//l + !!!!
int rand = l + (int) Math.random() * (r - l + 1);
swap(arr, rand, r);//枢纽放在最后面
//返回的p是个==区间范围的长度为2的数组[左区间,右区见]
int[] p = partition(arr, l, r);
quickSort(arr, l, p[0] - 1);
quickSort(arr, p[1] + 1, r);
}
}
private static int[] partition(int[] arr, int l, int r) {
//因为l指向的是第一个数,所以边界值应当是l-1
//因为r指向的数是枢纽,所以有边界是r
int less = l - 1;//左边界
int more = r;//右
while (l < more) {
//从当前值l开始,把小于pivot的放入less区间,大于的放入more区间,等于的就跳过
if (arr[l] < arr[r]) {
//左边界的下一个值和当前值交换,当前值后移
swap(arr,++less,l++);
} else if (arr[l] > arr[r]) {
//右边界的下一个和当前值交换,当前值不动
swap(arr, --more, l);
} else {//==下一个
l++;
}
}
//把在最后的pivot放到中间部分
swap(arr, more, r);
//返回==的区间
return new int[]{less+1,more};
}
public static void swap(int[] arr, int i, int j) {
int temp = arr[j];
arr[j] = arr[i];
arr[i] = temp;
}
public static void main(String[] args) {
int[] a = {5, 3, 6, 7, 8, 1, 4};
sort(a);
for (int i : a) {
System.out.println(i);
}
}
}
(3)尾递归
尾递归是一种递归的形式,其中递归调用发生在函数的最后一个操作中。在一些编程语言中,编译器可以对尾递归进行优化,将递归调用转换为迭代,从而避免栈溢出的风险。
public class Sort {
public static void sortMain(int[] arr) {
Random random = new Random();
sort(arr, 0, arr.length - 1, random);
}
public static void sort(int arr[], int low, int high, Random random) {
while (low < high) {
int p = partition(arr, low, high, random);
sort(arr, low, p - 1, random);
low = p + 1; // 尾递归的关键是更新递归参数,以便在下一次迭代中避免栈溢出
}
}
public static int partition(int arr[], int low, int high, Random random) {
int nextInt = low + random.nextInt(high - low + 1);
int pivot = arr[nextInt];
arr[nextInt] = arr[low];
arr[low] = pivot;
while (low < high) {
while (low < high && arr[high] >= pivot) {
high--;
}
arr[low] = arr[high];
while (low < high && arr[low] <= pivot) {
low++;
}
arr[high] = arr[low];
}
arr[low] = pivot;
return low;
}
public static void main(String[] args) {
int a[] = {5, 23, 7, 8, 1, 65, 68, 90, 34, 3, 0};
sortMain(a);
for (int i = 0; i < a.length; i++) {
System.out.print(a[i] + " ");
}
}
}
5.堆排序
1,是一个完全二叉树
public class Test01 {
public static void heapSort(int[] arr) {
if (arr == null && arr.length < 2) {
return;
}
//实现大根堆
for (int i = 0; i < arr.length; i++) {
heapInsert(arr, i);
}
//上面的实现大根堆是一个一个插入,逐步实现大根堆
//也可以直接用从上到下有序化直接全部实现
//直接全部排序的效率要高一点
/*
//从最后一个局部的跟几点开始,进行从上到下恢复大根堆
for(int i = (arr.length/2)-1;i>=0;i--){
heapify(arr, i, heapSize);
}
*/
//最大的数和末位数交换,实现排序
int heapSize = arr.length;
swap(arr, 0, --heapSize);
while (heapSize > 0) {
//冲新调整
heapify(arr, 0, heapSize);
swap(arr, 0, --heapSize);
}
}
//从上到下恢复大根堆
private static void heapify(int[] arr, int i, int heapSize) {
//i的孩子节点是i*2+1
int left = i * 2 + 1;
//如有孩子
while (left < heapSize) {
//bigest是左右孩子大的
int bigest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
//bigest在和父亲比,取大的那个
bigest = arr[i] > arr[bigest] ? i : bigest;
//若i还是最大的那个
if (bigest == i) {
break;
} else {
swap(arr, i, bigest);
i = bigest;
left = i * 2 + 1;
}
}
}
//从下到上形成大根堆
private static void heapInsert(int[] arr, int i) {
//当比父节点大,实现swap(i的父节点是(i-1)/2)
while (arr[i] > arr[(i - 1) / 2]) {
swap(arr, i, (i - 1) / 2);
i = (i - 1) / 2;
}
}
private static void swap(int[] arr, int i, int i1) {
int temp = arr[i];
arr[i] = arr[i1];
arr[i1] = temp;
}
public static void main(String[] args) {
int[] a = {5, 2, 7, 1, 6, 0, 7, 4, 2, 1};
heapSort(a);
for (int i : a) {
System.out.print(i+" ");
}
}
}
堆结构的应用-实现限制移动距离的排序
import java.util.PriorityQueue;
public class Test01 {
public static void heapSort(int[] arr,int k) {
//小根堆
PriorityQueue<Integer> small = new PriorityQueue<>();
//按k+1个数范围(前k+1个数的移动范围最大为k)依次确定位置
int i = 0;
for (; i <= k; i++) {
small.add(arr[i]);
}
int j = 0;
//出一个,进一个,保证k
for (; i < arr.length;i++, j++) {
arr[j] = small.poll();
small.add(arr[i]);
}
//剩下的依次弹出排序
while (!small.isEmpty()) {
arr[j++] = small.poll();
}
}
public static void main(String[] args) {
int[] a = {2,1,3,4,5,7,6};
heapSort(a,3);
for (int i : a) {
System.out.print(i+" ");
}
}
}
6.基数排序
public class RadixSort {
/**
* 优化的基数排序
*思想:10个桶,从左往右入桶(把该位置的数的个数累加到桶中,再计算出<=该位置的个数)
* 出桶(从原数组从右向左出桶,该位置的数的个数-1就是该数在辅助数组的位置)
*还原到原数组,进行按下一位入桶
*/
public static void radixSort(int[] nums) {
if (nums == null || nums.length < 2) {
return ;
}
//maxbits(nums)得到最大十进制数的位数
radixSort(nums, 0, nums.length-1, maxbits(nums));
}
private static void radixSort(int[] nums, int L, int R, int maxbits) {
//辅助空间
int[] bucket = new int[R - L + 1];
//桶数
final int radix = 10;
//入桶
for (int i = 1; i <= maxbits; i++) {//有多少位,进出多少次桶
int[] count = new int[radix];//初始化桶
for (int j = L; j <= R; j++) {
int digit = getDigit(nums[j], i);//得到第i位的数
count[digit]++;//入桶
}
//基数排序的优化,把<=i位的数累加
for (int k = 1; k < radix; k++) {
count[k] = count[k - 1] + count[k];
}
//从右向左出桶
for (int t = R; t >= L ; t--) {
int digit = getDigit(nums[t], i);//得到按第i个位置出桶
bucket[--count[digit]] = nums[t];//出桶的数放在辅助数组的第(数量-1个位置)
}
//还原到num数组,进行下一次入桶
for (int m = L; m <= R; m++) {
nums[m] = bucket[m];
}
}
}
//得到第i位的数
private static int getDigit(int num, int i) {
return (num / (int) Math.pow(10, i - 1)) % 10;
}
private static int maxbits(int[] nums) {
//得到最大的数
int max = Integer.MIN_VALUE;
for (int i = 0; i < nums.length; i++) {
max = Math.max(max, nums[i]);
}
//计算最大数的位数
int res = 0;
while (max != 0) {
res++;
max /= 10;
}
return res;
}
public static void main(String[] args) {
int[] n = {55, 2, 5, 765, 8, 114, 797, 310};
radixSort(n);
for (int i = 0; i < n.length; i++) {
System.out.print(n[i]+" ");
}
}
}
7.希尔排序
是一种基于
插入排序
的算法
每组相当于是以d为跨度的插入排序
不断缩小d实现不断缩小的局部排序
在局部有序后,最终d=1时,实现局部有序情况下的插入排序
特点:
1.速度比选择和插入快,并且数组越大,优势越明显
2.相比于快排和归并,虽然不如他们,但是性价比高,简单,且效率不一定慢很多
public static void sort(int[] a) {
int j;
int N = a.length;
// 进行不断缩小h,进行排序
for (int d = N/2; d >= 1; d /= 2) {
//每组进行排序
for (int i = d; i < N; i++) {
// 组内排序相当于插入排序
if (a[i] < a[i - d]) {
int temp = a[i];
for (j = i-d; j >= 0 && a[j] > temp; j -= d) {
//后移
a[j+d] = a[j];
}
a[j+d] = temp;
}
}
}
}
排序总结
二、二叉树
1.先序遍历
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<Integer>();
if(root!=null){
Stack<TreeNode> stack = new Stack();
stack.push(root);
while(!stack.isEmpty()){
root = stack.pop();
list.add(root.val);
if(root.right!=null){
stack.push(root.right);
}
if(root.left!=null){
stack.push(root.left);
}
}
}
return list;
}
}
2.后序遍历
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<Integer>();
if(root!=null){
Stack<TreeNode> stack1 = new Stack();
Stack<TreeNode> stack2 = new Stack();
stack1.push(root);
while(!stack1.isEmpty()){
root = stack1.pop();
stack2.push(root);
if(root.left!=null){
stack1.push(root.left);
}
if(root.right!=null){
stack1.push(root.right);
}
}
while(!stack2.isEmpty()){
list.add(stack2.pop().val);
}
}
return list;
}
}
3.中序遍历
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
Stack<TreeNode> stack = new Stack<TreeNode>();
List<Integer> list = new ArrayList<Integer>();
while(root!=null||!stack.isEmpty()){
if(root!=null){
stack.push(root);
root = root.left;
}else{
root = stack.pop();
list.add(root.val);
root = root.right;
}
}
return list;
}
}
4.二叉树最大宽度
(1)hashMap记录层数法
import java.util.*;
/*
* public class TreeNode {
* int val = 0;
* TreeNode left = null;
* TreeNode right = null;
* public TreeNode(int val) {
* this.val = val;
* }
* }
*/
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 求这可树的最大宽度
* @param head TreeNode类 树的根节点
* @return int整型
*/
public int getMaxWidth (TreeNode head) {
// write code here
if(head==null){
return 0;
}
Queue<TreeNode> queue = new LinkedList();
Map<TreeNode,Integer> map = new HashMap();
//当前层
int curLevel = 1;
//当前层节点数
int curNodes = 0;
//记录最大宽度
int max = Integer.MIN_VALUE;
queue.add(head);
map.put(head,1);
while(!queue.isEmpty()){
TreeNode curNode = queue.poll();
if(map.get(curNode)==curLevel){
curNodes++;
}else{
max = Math.max(max,curNodes);
curLevel++;
curNodes=1;
}
//处理当前节点的子节点
if(curNode.left!=null){
queue.add(curNode.left);
map.put(curNode.left,curLevel+1);
}
if(curNode.right!=null){
queue.add(curNode.right);
map.put(curNode.right,curLevel+1);
}
}
return Math.max(max,curNodes);
}
}
(1)变量记录层数法
import java.util.*;
/*
* public class TreeNode {
* int val = 0;
* TreeNode left = null;
* TreeNode right = null;
* public TreeNode(int val) {
* this.val = val;
* }
* }
*/
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 求这可树的最大宽度
* @param head TreeNode类 树的根节点
* @return int整型
*/
public int getMaxWidth (TreeNode head) {
// write code here
if (head == null) {
return 0;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.add(head);
//当前层的最后一个节点
TreeNode curEndNode = head;
//下一层的最后一个节点
TreeNode nextEndNode = null;
//当前层节点数
int curNodes = 0;
//最大宽度
int max = Integer.MIN_VALUE;
while (!queue.isEmpty()) {
TreeNode curNode = queue.poll();
if (curNode.left != null) {
queue.add(curNode.left);
nextEndNode = curNode.left;
}
if (curNode.right != null) {
queue.add(curNode.right);
nextEndNode = curNode.right;
}
if (curNode != curEndNode) {
curNodes++;
} else {
curNodes++;
max = Math.max(max, curNodes);
curNodes = 0;
curEndNode = nextEndNode;
nextEndNode = null;
}
}
return max;
}
}
5.二叉搜索树
(1)判断是否为二叉搜索树(非递归)
从最最下的节点开始,判断当前值和再左下值的大小。然后当前点变成pre。
import java.util.*;
/*
* public class TreeNode {
* int val = 0;
* TreeNode left = null;
* TreeNode right = null;
* }
*/
public class Solution {
/**
*
* @param root TreeNode类
* @return bool布尔型
*/
int preVal = Integer.MIN_VALUE;
public boolean isValidBST (TreeNode root) {
// write code here
if(root==null){
return true;
}
boolean isleftBST = isValidBST(root.left);
if(!isleftBST){
return false;
}
if(root.val<=preVal){
return false;
}else{
preVal = root.val;
}
return isValidBST(root.right);
}
}
(2)判断是否为二叉搜索树(递归)
package Tree;
public class SearchBT {
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
}
//内部类---返回类型
public static class ReturnType {
public boolean isSearch;
public int max;
public int min;
public ReturnType(boolean isSearch, int max, int min) {
this.isSearch = isSearch;
this.max = max;
this.min = min;
}
}
public static boolean isSearch(TreeNode root) {
return process(root).isSearch;
}
public static ReturnType process(TreeNode root) {
//base
if (root == null) {
//base的max和min不知道怎么处理---再拆黑盒的时候处理
return null;
}
ReturnType leftDate = process(root.left);
ReturnType rightDate = process(root.right);
//得到局部root的三个信息
int min = root.val;
int max = root.val;
if (leftDate != null) {
max = Math.max(max, leftDate.max);
min = Math.min(min, leftDate.min);
}
if (rightDate != null) {
max = Math.max(max, rightDate.max);
min = Math.min(min, rightDate.min);
}
boolean isSearch = true;
if (leftDate != null && (!leftDate.isSearch) || (leftDate.max >= root.val)) {
isSearch = false;
}
if (rightDate != null && (!rightDate.isSearch) || rightDate.min <= root.val) {
isSearch = false;
}
return new ReturnType(isSearch, max, min);
}
}
6.二叉完全树
(1)判断是否是二叉完全树(递归)
package Tree;
import java.util.LinkedList;
import java.util.Queue;
public class CBT {
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
}
public static boolean isCBT(TreeNode root) {
if (root == null) {
return true;
}
boolean notall = false;//事件:子节点不是双全
Queue<TreeNode> queue = new LinkedList<TreeNode>();
//两个子节点
TreeNode l = null;
TreeNode r = null;
queue.add(root);
while (!queue.isEmpty()) {
TreeNode cur = queue.poll();
l = cur.left;
r = cur.right;
//非完全的两个情况
if (
//前面有子节点不是双全的情况,后面就不能再有节点了
(notall && (l != null || r != null))
||
//没有左孩子,就不能有右孩子
(l == null && r != null)
) {
return false;
}
if (l != null) {
queue.add(l);
}
if (r != null) {
queue.add(r);
}
if (l == null && r == null) {
notall = true;
}
}
return true;
}
}
7.平衡二叉树
(1)判断是否是平衡二叉树(递归)
package Tree;
public class Balance {
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
}
//内部类---返回类型
public static class ReturnType {
public boolean isBalance;
public int high;
public ReturnType(boolean isBalance, int high) {
this.isBalance = isBalance;
this.high = high;
}
}
public static boolean isBalance(TreeNode root) {
return process(root).isBalance;
}
//执行方法
public static ReturnType process(TreeNode root) {
//base
if (root == null) {
return new ReturnType(true, 0);
}
//递归黑盒
ReturnType leftbalance = process(root.left);
ReturnType rightbalance = process(root.right);
//拆黑盒
int high = Math.max(leftbalance.high, rightbalance.high) + 1;
boolean isBalance = (leftbalance.isBalance && rightbalance.isBalance) && (Math.abs(leftbalance.high - rightbalance.high) < 2);
return new ReturnType(isBalance, high);
}
}
8.满二叉树
(1)满二叉树判断(递归)
package Tree;
public class FullBT {
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
}
public static class Info {
public int high;
public int nodes;
public boolean isF;
public Info(int high, int nodes, boolean isF) {
this.high = high;
this.nodes = nodes;
this.isF = isF;
}
}
public static boolean isFull(TreeNode root) {
return process(root).isF;
}
public static Info process(TreeNode root) {
if (root == null) {
return new Info(0, 0, true);
}
Info leftDate = process(root.left);
Info rightDate = process(root.right);
int high = Math.max(leftDate.high, rightDate.high) + 1;
int nodes = leftDate.nodes + rightDate.nodes + 1;
boolean isF = false;
if ((Math.pow(2, high) - 1) == nodes) {
isF = true;
}
return new Info(high, nodes, isF);
}
}
9.前缀树
/**
* 前缀树
*/
public class Code_TrieTree {
/**
* 前缀树节点
*/
public static class TrieNode {
int pass;//通过该节点的数量
int end;//以该节点结尾的数量
TrieNode[] next;//该节点通往下一路径节点的数组,next[0]-->a;当字符数量特别大时候用hashmap
public TrieNode() {
this.pass = 0;
this.end = 0;
this.next = new TrieNode[26];
}
}
public static class Trie {
private TrieNode root;//根节点
public Trie(){
root = new TrieNode();
}
//插入word
public void insert(String word) {
if (word == null) {
return;
}
char[] chars = word.toCharArray();
TrieNode curNode = root;//从root开始
root.pass++;
int index = 0;//路径索引0-25
for (int i = 0; i < chars.length; i++) {
index = chars[i] - 'a';
if (curNode.next[index] == null) {
curNode.next[index] = new TrieNode();
}
curNode = curNode.next[index];
curNode.pass++;
}
curNode.end++;
}
//这个word加入过几次
public int search(String word) {
if (word == null) {
return 0;
}
char[] chars = word.toCharArray();
TrieNode curNode = root;
int index = 0;
for (int i = 0; i < chars.length; i++) {
index = chars[i] - 'a';
if (curNode.next[index] == null) {
return 0;
}
curNode = curNode.next[index];
}
return curNode.end;
}
//以pre为前缀的字符串个数
public int preFixNumber(String pre) {
if (pre == null) {
return 0;
}
char[] chars = pre.toCharArray();
TrieNode curNode = root;
int index = 0;
for (int i = 0; i < chars.length; i++) {
index = chars[i] = 'a';
if (curNode.next[index] == null) {
return 0;
}
curNode = curNode.next[index];
}
return curNode.pass;
}
//删除
public void delete(String word) {
if (search(word) == 0) {
return;
}
char[] chars = word.toCharArray();
TrieNode curNode = root;
curNode.pass--;
int index = 0;
for (int i = 0; i < chars.length; i++) {
index = chars[i] - 'a';
//回收
if (--curNode.next[index].pass == 0) {
curNode.next[index] = null;
return;
}
curNode = curNode.next[index];
}
curNode.end--;
}
}
}
三 图
1.图的结构
(1)Node
public class Node {
public int value;//点的值
public int in;//入度
public int out;//出度
public ArrayList<Node> nexts;//出度的点
public ArrayList<Edge> edges;//出度的边
public Node(int value) {
this.value = value;
this.in = 0;
this.out = 0;
this.nexts = new ArrayList<>();
this.edges = new ArrayList<>();
}
}
(2)Eedg
public class Edge {
public int weight;//权重
public Node from;//起始边
public Node to;//结束边
public Edge(int weight, Node from, Node to) {
this.weight = weight;
this.from = from;
this.to = to;
}
}
(3)Graph
public class Graph {
public HashMap<Integer, Node> nodes;//点集
public HashSet<Edge> edges;//边集
public Graph() {
nodes = new HashMap<>();
edges = new HashSet<>();
}
}
2.将以其他方式表示的图,转化为自己方式表示的图,以方便处理
public class CreateGraph {
/**
* 因为图的表示方法很多,所以针对图的算法,都是把他给的图的表示方法,转换成自己写的图的表示,再去写算法
* 这个结构是符合所有图的表示的,所以并不是每个信息都是有用的,在实际算法中,不需要的数据可以不写
* 这个例子是吧[from,to,weight]这样一个N*3表示的二维数组表示的图变成自己写的图
* from-->fromNode的Key值
* weight--->边的权重
* @param matrix
* @return
*/
public static Graph createGraph(Integer[][] matrix) {
Graph graph = new Graph();
for (int i = 0; i < matrix.length; i++) {
int from = matrix[i][0];
int to = matrix[i][1];
int weight = matrix[i][2];
//把每次得到的两个节点放入graph的节点集
if (!graph.nodes.containsKey(from)) {
graph.nodes.put(from, new Node(from));
}
if (!graph.nodes.containsKey(to)) {
graph.nodes.put(to, new Node(to));
}
//拿到刚才创建出来的点,或者之前创建出来的点
Node fromNode = graph.nodes.get(from);
Node toNode = graph.nodes.get(to);
//创建边
Edge newEdge = new Edge(weight, fromNode, toNode);
//补全Node和Grapg的信息
fromNode.out++;
toNode.in++;
fromNode.nexts.add(toNode);
fromNode.edges.add(newEdge);
graph.edges.add(newEdge);
}
return graph;
}
}
3.图的广度优先遍历
/**
* 图的广度优先遍历
*/
public class Graph_BFS {
public static void bfs(Node node) {
if (node == null) {
return;
}
//辅助队列
Queue<Node> queue = new LinkedList<>();
//防止重复遍历
Set<Node> set = new HashSet<>();
queue.add(node);
set.add(node);
while (!queue.isEmpty()) {
Node cur = queue.poll();
//执行操作
System.out.println(cur.value);
//遍历cur的next集合
for (Node next : cur.nexts) {
if (!set.contains(next)) {
queue.add(next);
set.add(next);
}
}
}
}
}
4.图的深度优先遍历
/**
* 图的深度优先遍历
*/
public class Graph_DFS {
public static void bfs(Node node) {
if (node == null) {
return;
}
Stack<Node> stack = new Stack<Node>();
Set<Node> set = new HashSet<>();
stack.push(node);
set.add(node);
//处理
System.out.println(node.value);
while (!stack.isEmpty()) {
Node cur = stack.pop();
for (Node next : cur.nexts) {
if (!set.contains(next)) {
stack.push(cur);
stack.push(next);
set.add(next);
System.out.println(next.value);
break;//如果有需要继续深度遍历的节点,就break!!!
}
}
}
}
}
5.图的拓扑排序
//图的拓扑排序
public class Topo {
public static List<Node> getTopo(Graph graph) {
//存储当前节点及其入度的关系
Map<Node, Integer> inMap = new HashMap<>();
//存储可以执行操作的节点(入度为0)
Queue<Node> zeroQueue = new LinkedList<>();
//遍历-->填充inMap,并且把起始点放入队列
for (Node cur : graph.nodes.values()) {
inMap.put(cur, cur.in);
if (cur.in == 0) {
zeroQueue.add(cur);
}
}
//不断把in为0的节点放入res集,并且去除其对于next的影响
List<Node> res = new ArrayList<>();
while (!zeroQueue.isEmpty()) {
Node cur = zeroQueue.poll();
res.add(cur);
for (Node next : cur.nexts) {
inMap.put(next, inMap.get(next) - 1);
if (next.in == 0) {
zeroQueue.add(next);
}
}
}
return res;
}
}
6.最小生成树
(1)Kruskal—无向图
/**
* kruskal:在不成环的基础上,将最小边放入
*/
public class Kruskal {
//学习并查集之后,用并查集来代替这个
public static class Mysets {
//存储节点和其所在集合
public HashMap<Node, List<Node>> map;
//初始化每个节点的集合关系
public Mysets(List<Node> nodes) {
for (Node cur : nodes) {
List<Node> list = new ArrayList<>();
list.add(cur);
map.put(cur, list);
}
}
//判读是否为相同集合--->想要加入的边若是from和to节点都在同一个集合内,即为环
public boolean isSameSet(Node from, Node to) {
List<Node> fromSet = map.get(from);
List<Node> toSet = map.get(to);
return fromSet == toSet;
}
//若不为环,将此边的两个节点纳入集合
public void union(Node from, Node to) {
List<Node> fromSet = map.get(from);
List<Node> toSet = map.get(to);
for (Node toNode : toSet) {
fromSet.add(toNode);
map.put(toNode, fromSet);
}
}
}
//比较器
public static class EdgeComparator implements Comparator<Edge> {
@Override
public int compare(Edge o1, Edge o2) {
return o1.weight - o2.weight;
}
}
public static Set<Edge> kruskal(Graph graph) {
//初始化集合
List<Node> nodes = (List<Node>) graph.nodes.values();
Mysets mysets = new Mysets(nodes);
//得到最小边排序
PriorityQueue<Edge> priorityQueue = new PriorityQueue<>(new EdgeComparator());
for (Edge edge : graph.edges) {
priorityQueue.add(edge);
}
//结果集
Set<Edge> res = new HashSet<>();
//process
while (!priorityQueue.isEmpty()) {
Edge edge = priorityQueue.poll();
//判断是否是回路
if (!mysets.isSameSet(edge.from, edge.to)) {
res.add(edge);
mysets.union(edge.from, edge.to);
}
}
return res;
}
}
(2)Prim—无向图
//Prim:从任意一个点开始,在另一端未在集合内的情况下,在已解锁的边中将最小权重的边纳入
public class Prim {
public static Set<Edge> prim(Graph graph) {
//小根堆
PriorityQueue<Edge> priorityQueue = new PriorityQueue<>(new Kruskal.EdgeComparator());
//存储已经解锁过的点
Set<Node> set = new HashSet<>();
//结果集
Set<Edge> res = new HashSet<>();
//这个for是针对于非联通图
for (Node node : graph.nodes.values()) {
//处理当前联通图的第一个节点
//如果该点还没有解锁
if (!set.contains(node)) {
set.add(node);
//解锁边
for (Edge edge : node.edges) {
priorityQueue.add(edge);
}
}
//循环处理剩下的
while (!priorityQueue.isEmpty()) {
//弹出最小边
Edge edge = priorityQueue.poll();
//得到该边的另一个顶点
Node toNode = edge.to;
//如果不含有这个点
if (!set.contains(toNode)) {
set.add(toNode);
res.add(edge);
//解锁这个新的点的边
for (Edge nextEdge : toNode.edges) {
priorityQueue.add(nextEdge);
}
}
}
}
return res;
}
}
7.最短路径
(1)Dijkstra
//最短路径---Dijkstra:从起点开始在不断更新点的同时不断更新最短路径
public class Dijkstra {
public Map<Node, Integer> getDijkstra(Node head) {
//当起点到前节点的最短路径关系
Map<Node, Integer> distanceMap = new HashMap<>();
//已经判断过的点
Set<Node> selected = new HashSet<>();
//初始化
distanceMap.put(head, 0);
Node unselectedAndMinDistance = getUnselectedAndMinDistance(distanceMap, selected);
while (unselectedAndMinDistance != null) {
//得到这个最小的边值
Integer distance = distanceMap.get(unselectedAndMinDistance);
//加入这个点,更新到其他点的边
for (Edge edge : unselectedAndMinDistance.edges) {
//拿到当前点的对点
Node toNode = edge.to;
//head到此对点的距离为无穷---插入距离
if (!distanceMap.containsKey(toNode)) {
distanceMap.put(toNode, distance + edge.weight);
}
//不是无穷(之前有距离),需要比对之前的距离和现在的距离,从而更新
distanceMap.put(toNode, Math.min((distance + edge.weight), distanceMap.get(toNode)));
}
//标记这个加入的点
selected.add(unselectedAndMinDistance);
unselectedAndMinDistance = getUnselectedAndMinDistance(distanceMap, selected);
}
return distanceMap;
}
/**
* 得到未被选择过的点且路径最短的点
* @param distanceMap
* @param selected
* @return
*/
private Node getUnselectedAndMinDistance(Map<Node, Integer> distanceMap, Set<Node> selected) {
Integer min = Integer.MAX_VALUE;
Node minNode = null;
for (Map.Entry<Node, Integer> entry : distanceMap.entrySet()) {
Node key = entry.getKey();
Integer distance = entry.getValue();
if (!selected.contains(key) && distance < min) {
min = distance;
minNode = key;
}
}
return minNode;
}
}
四.查找
1.二分查找
(1)常规二分查找
int binarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1; // 注意
while(left <= right) {
int mid = left + (right - left) / 2;
if(nums[mid] == target)
return mid;
else if (nums[mid] < target)
left = mid + 1; // 注意
else if (nums[mid] > target)
right = mid - 1; // 注意
}
return -1;
}
(2)寻找左右边界的二分搜索
上面算法有什么缺陷?
答:⾄此,你应该已经掌握了该算法的所有细节,以及这样处理的原因。但是,这个算法存在局限性。
⽐如说给你有序数组 nums = [1,2,2,2,3],target 为 2,此算法返回的索引是 2,没错。但是如果我想
得到 target 的左侧边界,即索引 1,或者我想得到 target 的右侧边界,即索引 3,这样的话此算法是⽆
法处理的。
这样的需求很常⻅,你也许会说,找到⼀个 target,然后向左或向右线性搜索不⾏吗?可以,但是不好,
因为这样难以保证⼆分查找对数级的复杂度了。
int left_bound(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] == target) {
// 别返回,锁定左侧边界
right = mid - 1;
}
}
// 最后要检查 left 越界的情况
if (left >= nums.length || nums[left] != target) {
return -1;
}
return left;
}
int right_bound(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] == target) {
// 别返回,锁定右侧边界
left = mid + 1;
}
}
// 最后要检查 right 越界的情况
if (right < 0 || nums[right] != target) {
return -1;
}
return right;
}