尚硅谷图解Java数据结构和算法.pdf

1、线性查找

线性查找是一种非常简单的查找方式。查找思路是:从数组的一个元素出发,一个个地和要查找的值进行比较,如果发现有相同的元素就返回该元素的下标。反之返回-1(未找到)

  1. public class Demo1 {
  2. public static void main(String[] args) {
  3. int[] arr = {1, 11, -1, 0, 55};
  4. int result = seqSearch(arr, 1);
  5. if(result == -1) {
  6. System.out.println("数组中没有该元素");
  7. }else {
  8. System.out.println("该元素在数组的下标是:" + result);
  9. }
  10. }
  11. /**
  12. * 线性查找
  13. * @param arr 查找的数组
  14. * @param num 待查找的数字
  15. * @return 数字的索引
  16. */
  17. public static int seqSearch(int[] arr, int num) {
  18. for(int i = 0; i<arr.length; i++) {
  19. if(arr[i] == num) {
  20. return i;
  21. }
  22. }
  23. return -1;
  24. }
  25. }

2、二分查找

进行二分查找的数组必须为有序数组

  • 设置一个指向中间元素下标的变量mid,mid=(left + right)/2
  • 让要查找的元素和数组mid下标的元素进行比较
    • 如果查找的元素大于arr[mid],则left变为mid后面一个元素的下标
    • 如果查找的元素小于arr[mid],则right变为mid前一个元素的下标
    • 如果查找的元素等于arr[mid],则mid就是要查找元素所在的位置
  • 当left > rigth时,说明元素不在该数组中

image.png
image.png

  1. //注意:使用二分查找的前提是 该数组是有序的.
  2. public class BinarySearch {
  3. public static void main(String[] args) {
  4. int arr[] = { 1, 8, 10, 89,1000,1000, 1234 };
  5. int result = binarySearch(arr,0, arr.length - 1,10000);
  6. if(result == -1){
  7. System.out.println("数组中没有该元素");
  8. }else {
  9. System.out.println("该元素在数组的下标是:" + result);
  10. }
  11. }
  12. /**
  13. * 二分查找算法
  14. * @param arr 数组
  15. * @param left 左边的索引
  16. * @param right 右边的索引
  17. * @param findVal 要查找的值
  18. * @return 如果找到就返回下标,如果没有找到,就返回 -1
  19. */
  20. public static int binarySearch(int[] arr,int left,int right,int findVal){
  21. // 当 left > right 时,说明递归整个数组,但是没有找到
  22. if (left >right){
  23. return -1;
  24. }
  25. int mid = (left + right) / 2;
  26. int midVal = arr[mid];
  27. if (findVal > midVal){//向右递归
  28. return binarySearch(arr,mid + 1,right,findVal);
  29. }else if (findVal < midVal){//向左递归
  30. return binarySearch(arr, left, mid - 1, findVal);
  31. }else {
  32. return mid;
  33. }
  34. }
  35. }

但是有可能要查找的元素有多个。这时就需要在找到一个元素后,不要立即返回,而是扫描其左边和右边的元素,将所有相同元素的下标保存到一个数组中,然后一起返回

  1. //注意:使用二分查找的前提是 该数组是有序的.
  2. public class BinarySearch {
  3. public static void main(String[] args) {
  4. int arr[] = { 1, 8,10, 89,1000,1000, 1234 };
  5. List<Integer> reslist = binarySearch2(arr,0, arr.length - 1,1000);
  6. if (reslist.size() == 0){
  7. System.out.println("数组中没有该元素");
  8. }else {
  9. System.out.println("该元素在数组的下标是:" + reslist);
  10. }
  11. }
  12. //完成一个课后思考题:
  13. /*
  14. * 课后思考题: {1,8, 10, 89, 1000, 1000,1234} 当一个有序数组中,
  15. * 有多个相同的数值时,如何将所有的数值都查找到,比如这里的 1000
  16. *
  17. * 思路分析
  18. * 1. 在找到mid 索引值,不要马上返回
  19. * 2. 向mid 索引值的左边扫描,将所有满足 1000, 的元素的下标,加入到集合ArrayList
  20. * 3. 向mid 索引值的右边扫描,将所有满足 1000, 的元素的下标,加入到集合ArrayList
  21. * 4. 将Arraylist返回
  22. */
  23. public static List<Integer> binarySearch2(int[] arr, int left, int right, int findVal){
  24. // 当 left > right 时,说明递归整个数组,但是没有找到
  25. if (left >right){
  26. return new ArrayList<Integer>();
  27. }
  28. int mid = (left + right) / 2;
  29. int midVal = arr[mid];
  30. if (findVal > midVal){//向右递归
  31. return binarySearch2(arr,mid + 1,right,findVal);
  32. }else if (findVal < midVal){//向左递归
  33. return binarySearch2(arr, left, mid - 1, findVal);
  34. }else {
  35. List<Integer> resultIndexList = new ArrayList<>();
  36. //向mid 索引值的左边扫描,将所有满足 1000, 的元素的下标,加入到集合ArrayList
  37. int temp = mid -1;
  38. while (true){
  39. if (temp < 0 || arr[temp] != findVal) {
  40. break;
  41. }
  42. //否则,就temp 放入到 resIndexlist
  43. resultIndexList.add(temp);
  44. temp--;
  45. }
  46. resultIndexList.add(mid);
  47. //向mid 索引值的右边扫描,将所有满足 1000, 的元素的下标,加入到集合ArrayList
  48. temp = mid + 1;
  49. while (true){
  50. if (temp > arr.length - 1 || arr[temp] != findVal) {
  51. break;
  52. }
  53. resultIndexList.add(temp);
  54. temp++;
  55. }
  56. return resultIndexList;
  57. }
  58. }
  59. }

3、插值查找

在二分查找中,如果我们 要找的元素位于数组的最前端或者最后段,这时的查找效率是很低的。所以在二分查找至上,引入了插值查找,也是一种基于有序数组的查找方式
插值查找与二分查找的区别是:插值查找使用了一种自适应算法,用这种算法来计算mid。
mid的值在两种查找算法中的求法:

  • 二分查找:mid = (left + right)/2
  • 插值查找:mid = left + (right - left) * (num - arr[left]) / (arr[right] - arr[left])
    • 其中num为要查找的那个值

image.png

  1. public class InsertValueSearch {
  2. public static void main(String[] args) {
  3. int[] arr = new int[100];
  4. for (int i = 0; i < 100; i++) {
  5. arr[i] = i;
  6. }
  7. //int arr[] = { 1, 8,10, 89,1000,1000, 1234 };
  8. List<Integer> resListBinary = binarySearch2(arr,0, arr.length - 1,66 );
  9. List<Integer> reslistInsert = insertValueSearch(arr,0, arr.length - 1,66);
  10. if (resListBinary.size() == 0){
  11. System.out.println("数组中没有该元素");
  12. }else {
  13. System.out.println("二分查找:该元素在数组的下标是:" + resListBinary);
  14. }
  15. System.out.println("*************************");
  16. if (reslistInsert.size() == 0){
  17. System.out.println("数组中没有该元素");
  18. }else {
  19. System.out.println("插值查找:该元素在数组的下标是:" + reslistInsert);
  20. }
  21. }
  22. //编写插值查找算法
  23. //说明:插值查找算法,也要求数组是有序的
  24. /**
  25. *插值查找
  26. * @param arr 数组
  27. * @param left 左边索引
  28. * @param right 右边索引
  29. * @param findVal 查找值
  30. * @return 如果找到,就返回对应的下标,如果没有找到,返回-1
  31. */
  32. public static List<Integer> insertValueSearch(int[] arr,int left,int right,int findVal){
  33. System.out.println("插值查找次数~~");
  34. //注意:findVal < arr[0] 和 findVal > arr[arr.length - 1] 必须需要
  35. //否则我们得到的 mid 可能越界
  36. if(left > right || findVal < arr[0] || findVal > arr[arr.length - 1]){
  37. return new ArrayList<Integer>();
  38. }
  39. int mid = left + (right - left) * (findVal - arr[left]) / (arr[right] - arr[left]);
  40. int midVal = arr[mid];
  41. if (findVal > midVal){//向右递归
  42. return insertValueSearch(arr,mid + 1,right,findVal);
  43. }else if (findVal < midVal){//向左递归
  44. return insertValueSearch(arr, left, mid - 1, findVal);
  45. }else {
  46. List<Integer> resultIndexList = new ArrayList<>();
  47. //向mid 索引值的左边扫描,将所有满足 1000, 的元素的下标,加入到集合ArrayList
  48. int temp = mid -1;
  49. while (true){
  50. if (temp < 0 || arr[temp] != findVal) {
  51. break;
  52. }
  53. //否则,就temp 放入到 resIndexlist
  54. resultIndexList.add(temp);
  55. temp--;
  56. }
  57. resultIndexList.add(mid);
  58. //向mid 索引值的右边扫描,将所有满足 1000, 的元素的下标,加入到集合ArrayList
  59. temp = mid + 1;
  60. while (true){
  61. if (temp > arr.length - 1 || arr[temp] != findVal) {
  62. break;
  63. }
  64. resultIndexList.add(temp);
  65. temp++;
  66. }
  67. return resultIndexList;
  68. }
  69. }
  70. //二分查找
  71. public static List<Integer> binarySearch2(int[] arr, int left, int right, int findVal){
  72. System.out.println("二分查找被调用~");
  73. // 当 left > right 时,说明递归整个数组,但是没有找到
  74. if (left >right){
  75. return new ArrayList<Integer>();
  76. }
  77. int mid = (left + right) / 2;
  78. int midVal = arr[mid];
  79. if (findVal > midVal){//向右递归
  80. return binarySearch2(arr,mid + 1,right,findVal);
  81. }else if (findVal < midVal){//向左递归
  82. return binarySearch2(arr, left, mid - 1, findVal);
  83. }else {
  84. List<Integer> resultIndexList = new ArrayList<>();
  85. //向mid 索引值的左边扫描,将所有满足 1000, 的元素的下标,加入到集合ArrayList
  86. int temp = mid -1;
  87. while (true){
  88. if (temp < 0 || arr[temp] != findVal) {
  89. break;
  90. }
  91. //否则,就temp 放入到 resIndexlist
  92. resultIndexList.add(temp);
  93. temp--;
  94. }
  95. resultIndexList.add(mid);
  96. //向mid 索引值的右边扫描,将所有满足 1000, 的元素的下标,加入到集合ArrayList
  97. temp = mid + 1;
  98. while (true){
  99. if (temp > arr.length - 1 || arr[temp] != findVal) {
  100. break;
  101. }
  102. resultIndexList.add(temp);
  103. temp++;
  104. }
  105. return resultIndexList;
  106. }
  107. }
  108. }

4、斐波那契(黄金分割)查找image.png
image.png

代码:

  1. package com.luyi.DataStructures.recursion;
  2. import java.lang.reflect.Array;
  3. import java.util.Arrays;
  4. /**
  5. * 斐波那契查找
  6. */
  7. public class FibonacciSearch {
  8. public static int maxSize = 20;
  9. public static void main(String[] args) {
  10. int[] arr = {1,8,10,89,1000,1234};
  11. System.out.println(fibSearch(arr, 1234));// 5
  12. }
  13. // 因为后面我们mid = low+F(k - 1)-1. 需要使用斐波那契数列, 因此我们需要先获取到斐波那契数列
  14. // 用非递归的方法得到一个大小为20的斐波那契数列
  15. public static int[] fbn(){
  16. int[] f = new int[maxSize];
  17. f[0] = 1;
  18. f[1] = 1;
  19. for (int i = 2; i < maxSize; i++){
  20. f[i] = f[i - 1] + f[i - 2];
  21. }
  22. return f;
  23. }
  24. // 非递归的方式编写斐波那契数列查找算法
  25. /**
  26. *
  27. * @param a
  28. * @param key 需要查找的值
  29. * @return 返回对应的下标 没有则返回-1
  30. */
  31. public static int fibSearch(int[] a, int key){
  32. int low = 0;
  33. int high = a.length -1;
  34. int k = 0; // 表示斐波那契分割数值的下标 mid = low+F(k - 1)-1
  35. int mid = 0;
  36. int[] f = fbn(); // 获取斐波那契数列
  37. // 获取斐波那契分割数值的下标
  38. while (high > f[k]-1){
  39. k++;
  40. }
  41. // 因为 f[k] 可能大于数组a 的长度 需要一个Array类 构造一个新的数组 并指向temp[]
  42. int[] temp = Arrays.copyOf(a, f[k]); // 多出来的部分用0补起来
  43. // 实际上需要使用a最后的数填充temp
  44. // 举例 :
  45. // temp:{1,8,10,89,1000,1234,0,0} =>temp:{1,8,10,89,1000,1234,1234,1234}
  46. for (int i = high + 1;i < temp.length; i++){
  47. temp[i] = a[high];
  48. }
  49. // 使用while循环查找我们的key
  50. while (low <= high) {
  51. mid = low + f[k-1]-1;
  52. if (key < temp[mid]){//说明应该向数组的前一部分查找(左边)
  53. high = mid -1;
  54. // 为什么是k--
  55. // 1 全部元素 = 前面的元素 + 后边的元素
  56. // 2 f[k] = f[k-1] + f[k-2];
  57. // 因为前面有f[k-1]个元素,所以我们继续拆分 f[k-1] = f[k-2] + f[k-3];
  58. // 即在f[k-1] 的前面继续查找 k--
  59. // 即下次在循环的时候 mid = f[k-1-1] -1
  60. k--;
  61. }else if ( key > temp[mid]){ // 我们继续向数组的右边查找
  62. low = mid + 1;
  63. // 为什么是k-2
  64. // 1 全部元素 = 前面的元素 + 后边的元素
  65. // 2 f[k] = f[k-1] + f[k-2];
  66. // 3 因为后面有f[k-2] 所以加足拆分 f[k -2] = f[k-3] + f[k-4];
  67. // 4 即在f[k-2] 的前面继续查找
  68. // 下次循环mid = f[k-1-2] -1
  69. k -= 2;
  70. }else { // 找到
  71. // 需要确定返回的是哪个下标
  72. if (mid <= high){
  73. return mid;
  74. }else {
  75. return high;
  76. }
  77. }
  78. }
  79. return -1; // 没有找到
  80. }
  81. }