数据结构与算法

一 排序

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值