• 数据结构和算法


    一、认识时间复杂度

    用大Ο记号表示算法的时间性能。  ​​​​

    二、二进制与整数之间的转换

    1、正整数与二进制互转

     除2之后将余数倒序排列, 直到商小于1

    201 / 2 = 100······1

    100 / 2 = 50  ······0

    50 / 2 = 25    ······0

    25 / 2 = 12    ······1

    12 / 2 = 6      ······0

    6 / 2 = 3        ······0

    3 / 2 = 1        ······1

    1 / 2 = 0        ······1    (商小于1,结束计算并将余数倒序排列)

    得到:201(十进制) = 11001001(二进制)

    2、负整数转二进制

    1)得到取绝对值的整数的二进制。 如 -201,先得到201的二进制11001001

    2)取反。得00110110

    3)加1。  得00110111

    3、小数部分转二进制

    1)小数部分乘2后将整数部分顺序排列。 如0.125,整数部分顺序排列得001

    2)前面加0.       得0.001

     三、排序算法

    排序算法大体可分为两种:

    • 一种是比较排序,时间复杂度O(nlogn) ~ O(n^2),主要有:冒泡排序,选择排序,插入排序,归并排序,堆排序,快速排序等。
    • 另一种是非比较排序,时间复杂度可以达到O(n),主要有:计数排序,基数排序,桶排序等。

    下面给出常见比较算法的排序性能

    1、交换方法

    1. //交换位置
    2. public static void swap(int[] arr,int i,int j){
    3. if(arr[i] > arr[j]){
    4. int num = arr[i];
    5. arr[i] = arr[j];
    6. arr[j] = num;
    7. }
    8. }
    9. //交换值
    10. //异或交换效率快
    11. //两个不同内存的变量(两个变量都是等与10),交换结果还是10
    12. //两个相同内存的变量(两个变量都是等与10),交换结果就为0
    13. public static void swap1(int[] arr,int i,int j){
    14. /**
    15. * 异或左边arr[i] = 甲
    16. * 异或右边arr[j] = 已
    17. * 等号左边arr[i] = 甲 ^ 已
    18. */
    19. arr[i] = arr[i] ^ arr[j];
    20. /**
    21. * (上面的公式得来的)异或左边arr[i] = 甲 ^ 已
    22. * 异或右边arr[j] = 已
    23. * 异或运算相等为0,不同为1
    24. * 等号左边arr[j] = 甲 ^ 已 ^ 已 = 甲
    25. */
    26. arr[j] = arr[i] ^ arr[j];
    27. /**
    28. * 异或左边arr[i] = 甲 ^ 已
    29. * (由上面第二个公式得来的)异或右边arr[j] = 甲
    30. * 异或运算相等为0,不同为1
    31. * 等号左边arr[i] = 甲 ^ 已 ^ 甲 = 已
    32. */
    33. arr[i] = arr[i] ^ arr[j];
    34. }

    2、选择排序

    算法描述

    1. 在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
    2. 从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
    3. 重复第二步,直到所有元素均排序完毕。

     具体代码:

    1. //交换位置
    2. public static void swap(int[] arr,int i,int j){
    3. if(arr[i] > arr[j]){
    4. int num = arr[i];
    5. arr[i] = arr[j];
    6. arr[j] = num;
    7. }
    8. }
    9. //选择排序
    10. public static int[] xzpx(int[] arr){
    11. if(arr == null || arr.length < 2){
    12. return arr;
    13. }
    14. for(int i = 0;i < arr.length;i++){
    15. int minIndex = i;
    16. for(int j = i + 1;j < arr.length;j++){
    17. if(arr[j] < arr[minIndex]){
    18. minIndex = j;
    19. }
    20. }
    21. swap(arr,i,minIndex);
    22. }
    23. return arr;
    24. }

    3、冒泡排序 

    冒泡排序也是通过遍历比较左右值得大小,例如排升序即左值大于右值交换,最后最大值即排到最右边。(每次遍历一次都能找到最大的数)

    在这里插入图片描述

     实现代码:

    1. //交换位置
    2. public static void swap(int[] arr,int i,int j){
    3. if(arr[i] > arr[j]){
    4. int num = arr[i];
    5. arr[i] = arr[j];
    6. arr[j] = num;
    7. }
    8. }
    9. //冒泡排序
    10. public static int[] mppx(int[] arr){
    11. if(arr == null || arr.length < 2){
    12. return arr;
    13. }
    14. for(int i = 0;i < arr.length;i++){
    15. for(int j = 0;j < arr.length - i - 1;j++){
    16. if(arr[j] > arr[j + 1]){
    17. swap(arr,j,j + 1);
    18. }
    19. }
    20. }
    21. return arr;
    22. }

    4、插入排序 

    直接插入排序就是把待排序的元素逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。

    在这里插入图片描述

    1. //插入排序
    2. public static int[] crpx(int[] arr){
    3. for(int i = 1;i < arr.length;i++){ //0 ~ i是有序数组
    4. for(int j = i - 1;j >= 0;j--){
    5. if(arr[j + 1] < arr[j]){
    6. swap(arr,j,j + 1);
    7. }
    8. }
    9. }
    10. return arr;
    11. }

     5、归并排序

     

    1. //归并排序
    2. public static void mergeSort(int[] arr,int left,int right){
    3. if(left == right){
    4. return;
    5. }
    6. int mid = left + ((right - left) >> 1);
    7. //向左递归进行分解
    8. mergeSort(arr,left,mid);
    9. //向右递归进行分解
    10. mergeSort(arr,mid + 1,right);
    11. //合并
    12. merge(arr,left,mid,right);
    13. }
    14. /**
    15. * 合并的方法(治)
    16. * @param arr 排序的原始数组
    17. * @param left 左边有序序列的初始索引
    18. * @param mid 中间索引
    19. * @param right 右边的索引
    20. */
    21. public static void merge(int[] arr,int left,int mid,int right){
    22. int l = left; //左边有序数组的左指针
    23. int j = mid + 1; //右边有序数组的左指针
    24. int i = 0; //指向temp数组的指针
    25. int[] temp = new int[right - left + 1]; //用来做中转的数组
    26. //步骤一:
    27. //先把左右两边(有序)的数据按照规则填充到temp数组
    28. //直到左右两边的有序序列,有一边处理完毕为止
    29. while(l <= mid && j <= right){
    30. temp[i++] = arr[l] <= arr[j] ? arr[l++] : arr[j++];
    31. }
    32. //如果左边还有剩余的数据就依次全部填充到temp数组中
    33. while(l <= mid){
    34. temp[i++] = arr[l++];
    35. }
    36. //如果右边还有剩余的数据就依次全部填充到temp数组中
    37. while(j <= right){
    38. temp[i++] = arr[j++];
    39. }
    40. 步骤二:
    41. //将temp数组的元素拷贝到arr中
    42. //注意,并不是每次都拷贝所有
    43. i = 0;
    44. int m = left;
    45. //第一次合并 m = 0,right = 1
    46. //第二次合并 m = 2,right = 3
    47. //第三次合并 m = 0,right = 3
    48. //最后一次合并 m = 0,right = arr.length
    49. while(m <= right){
    50. arr[m++] = temp[i++];
    51. }
    52. }

     步骤一:图解,要将[4,5,7,8]和[1,2,3,6]两个已经有序的子序列,合并为最终序列[1,2,3,4,5,6,7,8]

     步骤二:图解,

     

    5.1、小和问题和逆序问题

    小和问题:

    图解:

    用暴力算法求小和(时间复杂度O(N^2)):

    1. //暴力解法求小和
    2. public static int smallAnd(int[] arr){
    3. int num = 0;
    4. for(int i = 1;i < arr.length;i++){
    5. for(int j = 0;j < i;j++){
    6. if(arr[j] < arr[i]){
    7. num += arr[j];
    8. }
    9. }
    10. }
    11. return num;
    12. }

     用递归排序来求小和(时间复杂度为O(logN)):

    图解:

     下面的步骤缺一不可:如果把第一个方法去掉就会得出的结果就是有错误的

    1. //用递归返回小和
    2. public static int samllAnd(int[] arr){
    3. if(arr == null || arr.length < 2){
    4. return 0;
    5. }
    6. return recursionSmallAnd(arr,0,arr.length - 1);
    7. }
    8. //用递归排序来解决小和问题
    9. public static int recursionSmallAnd(int[] arr,int left,int right){
    10. if(left == right){
    11. return 0;
    12. }
    13. int mid = left + ((right - left) >> 1);
    14. return recursionSmallAnd(arr,left,mid) +
    15. recursionSmallAnd(arr,mid + 1,right) +
    16. mergeRecursion(arr,left,mid,right);
    17. }
    18. public static int mergeRecursion(int[] arr,int left,int mid,int right){
    19. int l = left;
    20. int r = mid + 1;
    21. int num = 0;
    22. int[] temp = new int[arr.length];
    23. int sum = 0;
    24. while(l <= mid && r <= right){
    25. //(r - mid + 1):表示右边的数组有几个数是大于左边第一个下标的数
    26. // (r - mid + 1):表示右边的数组有几个数是大于左边第二个下标的数
    27. // (r - mid + 1):表示右边的数组有几个数是大于左边第l个下标的数
    28. sum += arr[l] < arr[r] ? (r - mid + 1) * arr[l] : 0;
    29. temp[num++] = arr[l] <= arr[r] ? arr[l++] : arr[r++];
    30. }
    31. while(l <= mid){
    32. temp[num++] = arr[l++];
    33. }
    34. while(r <= right){
    35. temp[num++] = arr[r++];
    36. }
    37. num = 0;
    38. int m = left;
    39. while(m <= right){
    40. arr[m++] = temp[num++];
    41. }
    42. return sum;
    43. }

    逆序问题: 

    在一个数组中,左边的数比右边的大,则两个数构成一个逆序对,请算出总共有多少个逆序对。

    暴力求解: 

    1. //暴力求解逆序对
    2. public static int reversePair(int[] arr){
    3. int number = 0;
    4. for(int i = 0;i < arr.length - 1;i++){
    5. for(int j = i + 1;j < arr.length;j++){
    6. if(arr[j] < arr[i]){
    7. number++;
    8. }
    9. }
    10. }
    11. return number;
    12. }

     归并排序求解:

    逆向思维:左边有几个数大于右边的这个数

    1. //递归排序求解逆序对
    2. public static int recursionReversePair(int[] arr){
    3. if(arr == null || arr.length < 1){
    4. return 0;
    5. }
    6. return recursionSort(arr,0,arr.length - 1);
    7. }
    8. //递归排序
    9. public static int recursionSort(int[] arr,int left,int right){
    10. if(left > right){
    11. return 0;
    12. }
    13. int median = left + ((right - left) >> 1);
    14. return recursionSort(arr,left,median) +
    15. recursionSort(arr,median + 1,right) +
    16. recursionMerge(arr,left,median,right);
    17. }
    18. //合并方法
    19. public static int recursionMerge(int[] arr,int left,int median,int right){
    20. int l = left;
    21. int r = median + 1;
    22. int number = 0;
    23. int[] array = new int[arr.length];
    24. int sum = 0;
    25. while(l <= median && r <= right){
    26. //左边有几个数大于右边
    27. sum += arr[l] > arr[r] ? l + 1 : 0;
    28. array[number++] = arr[l] <= arr[r] ? arr[l++] : arr[r++];
    29. }
    30. while(l <= median){
    31. array[number++] = arr[l++];
    32. }
    33. while(r <= right){
    34. array[number++] = arr[r++];
    35. }
    36. number = 0;
    37. int anotherNumber = left;
    38. while(anotherNumber <= right){
    39. arr[anotherNumber++] = array[number++];
    40. }
    41. return sum;
    42. }

    6、快速排序

    下次回来继续学习:

    1. public static void quickSort(int[] arr){
    2. if(arr == null || arr.length < 2){
    3. return;
    4. }
    5. quickSort(arr,0,arr.length - 1);
    6. }
    7. public static void quickSort(int[] arr,int L,int R){
    8. if(L < R){
    9. swap(arr,L + (int)(Math.random() * (R - L + 1)),R);
    10. int[] p = partition(arr,L,R);
    11. quickSort(arr,L,p[0] - 1);
    12. quickSort(arr,p[1] + 1,R);
    13. }
    14. }
    15. public static int[] partition(int[] arr,int L,int R){
    16. int less = L - 1;
    17. int more = R;
    18. while(L < more){
    19. if (arr[L] < arr[R]) {
    20. swap(arr,++less,L++);
    21. }else if(arr[L] > arr[R]){
    22. swap(arr,--more,L);
    23. }else{
    24. L++;
    25. }
    26. }
    27. swap(arr,more,R);
    28. return new int[]{less + 1,more};
    29. }
    30. public static void swap(int[] arr,int l,int r){
    31. int number = arr[l];
    32. arr[l] = arr[r];
    33. arr[r] = number;
    34. }

    7、堆排序 

    堆的性质:

    ① 是一棵完全二叉树
    ② 每个节点的值都大于或等于其子节点的值,为最大堆;反之为最小堆。

    堆储存方式:

     图二:

    四、查找算法 

    1、二分查找

    二分查找的时间复杂度为 O(log2n)。

    什么是 log2(n)

    • 表示的是 以 2 的多少次方等于 n
    • 数学上叫:以 2 为底 n 的 对数

    1. /**
    2. * 二分查找
    3. * @param arr 数组
    4. * @param left 左边的索引
    5. * @param right 右边的索引
    6. * @param num 要查找的数
    7. * @return 如果找到就返回下标,如果没有找到,就返回-1
    8. */
    9. public static int efcz(int[] arr,int left,int right,int num){
    10. //当left > right时,说明递归整个数组,但是没有找到
    11. if(left > right){
    12. return -1;
    13. }
    14. int mid = (left + right) / 2;
    15. int midVal = arr[mid];
    16. if(num > midVal){ //向右递归
    17. return efcz(arr,mid + 1,right,num);
    18. }else if(num < midVal){ //向左递归
    19. return efcz(arr,left,mid,num);
    20. }else{
    21. return mid;
    22. }
    23. }

    改进二分查找方法,返回多个 与num元素相同的元素下标

    1. /**
    2. * 二分查找 (查找多个元素)
    3. * @param arr 数组
    4. * @param left 左边的索引
    5. * @param right 右边的索引
    6. * @param number 要查找的数
    7. * @return 如果找到就返回下标,如果没有找到,就返回null
    8. */
    9. public static ArrayList binarySearch(int[] arr, int left, int right, int number){
    10. //当left > right时,说明递归整个数组,但是没有找到
    11. if(left > right){
    12. return null;
    13. }
    14. ArrayList list = new ArrayList<>();
    15. int median = left + ((right - left) >> 1);
    16. if(number > arr[median]){
    17. return binarySearch(arr,median + 1,right,number);
    18. }else if(number < arr[median]){
    19. return binarySearch(arr,left,median,number);
    20. }else{
    21. list.add(median);
    22. //向median索引值的左边扫描,将所有满足number的元素下标加入到ArrayList集合中
    23. int anotherNumber = median - 1;
    24. while(arr[anotherNumber] == number){
    25. list.add(anotherNumber--);
    26. }
    27. //向median索引值的右边扫描,将所有满足number的元素下标加入到集合ArrayList集合中
    28. anotherNumber = median + 1;
    29. while(arr[anotherNumber] == number){
    30. list.add(anotherNumber++);
    31. }
    32. }
    33. return list;
    34. }

    2、递归方法 

    2.1、左移(<<),右移(>>)

    左移 <<:

     左移运算有乘以2的N次方的效果。一个数向左移动1位,就相当于乘以2的1次方,移动两位就相当于乘以2的2次方,也就是乘以4。位移操作在实际运算时远远快于乘法操作,所以在某些对运算速度要求非常高的场合,可以考虑用左移代替乘以2的N次方的乘法操作。

    右移 >>:

    对于正数而言,带符号右移之后产生的数字确实等于除以2的N次方;

    对于负数而言,带符号右移的效果分为两种情况:

    如果这个负数是“2的N次方”的整数倍,那么带符号右移N位的效果也等于除以2的N次方。举个例子:我们还是把N的值设为3,如果对于“-16”来说,它是“2的3次方”的整数倍,那么带符号右移3位的结果是-2,这个结果相当于“-16除以2的3次方”。

    如果这个负数不是“2的N次方”的整数倍,那么右移N位之后,是在除以2的N次方的结果之上还要减去1。比如,对于-15来说,它不是“2的3次方”的整数倍,那么带符号右移3位的结果是-2,这个运算结果其实就是“-15被2的3次方整除再减去1”。

    2.2、求最大值

    1. //递归找最大值
    2. public static int zdzdg(int[] arr,int left,int right){
    3. if(left == right){
    4. return arr[left];
    5. }
    6. int mid = left + ((right - left) >> 1);
    7. int leftMax = zdzdg(arr,left,mid);
    8. int rightMax = zdzdg(arr,mid + 1,right);
    9. return Math.max(leftMax,rightMax);
    10. }

     2.3、master公式

    五、对数器

    1. //对数器
    2. public static int[] generateRandomArray(int maxSize,int maxValue){
    3. //Math.random() -> [0,1) 所有的小数,等概率返回一个
    4. //Math.random() * N -> [0,N) 所有小数,等概率返回一个
    5. //(int)(Math.random() * N) -> [0,N-1] 所有的整数,等概率返回一个
    6. int[] arr = new int[(int)((maxSize + 1) * Math.random())]; //长度随机
    7. for(int i = 0;i < arr.length;i++){
    8. arr[i] = (int)((maxValue + 1) * Math.random()) - (int)(maxValue * Math.random());
    9. }
    10. return arr;
    11. }

    五、数据结构

    1、二叉树

    完全二叉树:

    2、堆结构

  • 相关阅读:
    数据库连接池
    1740. 找到⼆叉树中的距离
    查找浏览器中保存的密码
    中秋发祝福?一套程序让你成为【相亲相爱一家人】群里最靓的仔
    手把手Java爬虫教学 - 8. 项目2 - 数据库表设计 & 爬虫代码实现
    消除注释C++
    abp(net core)+easyui+efcore实现仓储管理系统——ABP升级7.3下(五十九)
    浅谈 AI 大模型的崛起与未来展望:马斯克的 xAI 与中国产业发展
    企业如何建立全链路数字化体系,才能让卖货更容易?
    vue的由来、vue教程和M-V-VM架构思想、vue的使用、nodejs
  • 原文地址:https://blog.csdn.net/A1916403680/article/details/127202999