数组的查找
线性查找
概念
线性查找也叫顺序查找,这是最基本的一种查找方法,从给定的值中进行搜索,从一端开始逐一检查每个元素,直到找到所需元素的过程。
元素序列的排列可以有序,也可以无序。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| public class Test01 { public static void main(String[] args) { int[] arr = {45, 62, 15,62, 78, 30}; int index = sequentialSearch01(arr, 62); System.out.println("指定元素首次出现的下标位置:" + index); List<Integer> indexList = sequentialSearch02(arr, 62); System.out.println("指定元素出现的下标位置的集合:" + Arrays.toString(indexList.toArray())); }
public static int sequentialSearch01(int[] arr,int value){ for (int i = 0; i < arr.length; i++) { if(arr[i] == value){ return i; } } return -1; }
public static List<Integer> sequentialSearch02(int[] arr,int value){ List<Integer> list = new ArrayList<>(); for (int i = 0; i < arr.length; i++) { if(arr[i] == value){ list.adds(i); } } return list; } }
|
二分法查找
概念
二分查找(Binary Search)算法,也叫折半查找算法。
当要从一个序列中查找一个元素的时候,二分查找是一种非常快速的查找算法。
二分查找是针对有序数据集合的查找算法,如果是无序数据集合就遍历查找。
二分查找之所以快速,是因为它在匹配不成功的时候,每次都能排除剩余元素中一半的元素。因此可能包含目标元素的有效范围就收缩得很快,而不像顺序查找那样,每次仅能排除一个元素。
原理
比如有一个有序表数组[1,3,5,7,9,11,13,15,17,19,21],它是按照从小到大的顺序来进行排列的,现在需要在该有序表中查找元素19,步骤如下:
首先设置两个指针low和high,分别指向数据集合的第一个数据元素1(位序为0)和最后一个数据元素21(位序为10)。
然后把整个数据集合长度分成两半,并用一个指针指向它们的临界点,所以定义指针mid指向了中间元素11(位序5),也就是说mid=(high+low)/2,其中high和low都代表所指向的元素的位序,如下图:
接着,将mid所指向的元素(11)与待查找元素(19)进行比较。
因为19大于11,说明待查找的元素(19)一定位于mid和high之间。所以继续折半,则low = mid+1,而mid = (low+high)/2,结果如下图:
- 接着,又将mid所指向的元素(17)与待查找元素(19)进行比较,由于19大于17,所以继续折半,则low = mid+1,而mid = (low+high)/2,结果如下图:
最后,又将mid所指向的元素(19)与待查找元素(19)进行比较,结果相等,则查找成功,返回mid指针指向的元素的位序。
如果查找的元素值不是19,而是20,那么在最后一步之前还得继续折半查找,最后出现的情况如下图:
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
| public class Test01 { public static void main(String[] args) {
int[] arr = {1,2,3,4,5,6,7,8,9,11,11,11,11,11,11};
int index = binarySearch01(arr, 11); System.out.println("指定元素出现的下标位置:" + index);
List<Integer> indexList = binarySearch02(arr, 11); System.out.println("指定元素出现的下标位置的集合:" + Arrays.toString(indexList.toArray()));
index = recursionbinarySearch01(arr, 0, arr.length-1, 11); System.out.println("递归方式 - 指定元素出现的下标位置:" + index);
indexList = recursionbinarySearch02(arr, 0, arr.length-1, 11); System.out.println("递归方式 - 指定元素出现的下标位置的集合:" + Arrays.toString(indexList.toArray())); }
public static int binarySearch01(int[] arr,int val){
int low = 0; int high = arr.length-1;
while(low <= high){ int mid = (low + high)/2;
if(val > arr[mid]){ low = mid+1; }else if(val < arr[mid]){ high = mid-1; }else{ return mid; } } return -1; }
public static List<Integer> binarySearch02(int[] arr,int val){
int low = 0; int high = arr.length-1;
while(low <= high){ int mid = (low + high)/2;
if(val > arr[mid]){ low = mid+1; }else if(val < arr[mid]){ high = mid-1; }else{ List<Integer> list = new ArrayList<>(); list.add(mid);
int index = mid + 1; while(index < arr.length){ if(arr[index] == val){ list.add(index); }else{ break; } index++; } index = mid-1; while(index >= 0){ if(arr[index] == val){ list.add(index); }else{ break; } index--; } return list; } } return null; }
public static int recursionbinarySearch01(int[] arr,int low,int high,int val){
if(val < arr[low] || val > arr[high] || low > high){ return -1; }
int mid = (low + high)/2;
if(val > arr[mid]){ return recursionbinarySearch01(arr, mid+1, high, val); }else if(val < arr[mid]){ return recursionbinarySearch01(arr, low, mid-1, val); }else{ return mid; } }
public static List<Integer> recursionbinarySearch02(int[] arr,int low,int high,int val){
if(val < arr[low] || val > arr[high] || low > high){ return null; }
int mid = (low + high)/2;
if(val > arr[mid]){ return recursionbinarySearch02(arr, mid+1, high, val); }else if(val < arr[mid]){ return recursionbinarySearch02(arr, low, mid-1, val); }else{ List<Integer> list = new ArrayList<>(); list.add(mid);
int index = mid + 1; while(index < arr.length){ if(arr[index] == val){ list.add(index); }else{ break; } index++; } index = mid-1; while(index >= 0){ if(arr[index] == val){ list.add(index); }else{ break; } index--; } return list; } } }
|
优缺点
优点:速度快,不占空间,不开辟新空间
缺点:必须是有序的数组,数据量太小没有意义
插值查找
概念
从折半查找中可以看出,折半查找的查找效率还是不错的。可是为什么要折半呢?为什么不是四分之一、八分之一呢?
打个比方,在牛津词典里要查找“apple”这个单词,会首先翻开字典的中间部分,然后继续折半吗?肯定不会,对于查找单词“apple”,我们肯定是下意识的往字典的最前部分翻去,而查找单词“zero”则相反,我们会下意识的往字典的最后部分翻去。
所以在折半查找法的基础上进行改造就出现了插值查找法,也叫做按比例查找。所以插值查找与折半查找唯一不同的是在于mid的计算方式上,它的计算方式为:
int mid = low + (high - low) * (val- arr[low]) / (arr[high] - arr[low])
这样就能快速定位目标数值所在的索引,比二分查找可以更快速实现查找。
自适应获取mid,也就是自适应查找点。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
| public class Test01 { public static void main(String[] args) { int[] arr = {1,2,3,4,5,6,7,8,9,11,11,11,11,11,11};
int index = insertSearch01(arr, 11); System.out.println("指定元素出现的下标位置:" + index);
List<Integer> indexList = insertSearch02(arr, 11); System.out.println("指定元素出现的下标位置的集合:" + Arrays.toString(indexList.toArray()));
index = recursionInsertSearch01(arr, 0, arr.length-1, 11); System.out.println("递归方式 - 指定元素出现的下标位置:" + index);
indexList = recursionInsertSearch02(arr, 0, arr.length-1, 11); System.out.println("递归方式 - 指定元素出现的下标位置的集合:" + Arrays.toString(indexList.toArray())); }
public static int insertSearch01(int[] arr,int val){
int low = 0; int high = arr.length-1;
while(low <= high){ int mid = low + (high - low) * (val - arr[low])/(arr[high] - arr[low]);
if(val > arr[mid]){ low = mid+1; }else if(val < arr[mid]){ high = mid-1; }else{ return mid; } } return -1; }
public static List<Integer> insertSearch02(int[] arr,int val){
int low = 0; int high = arr.length-1;
while(low <= high){ int mid = low + (high - low) * (val - arr[low])/(arr[high] - arr[low]);
if(val > arr[mid]){ low = mid+1; }else if(val < arr[mid]){ high = mid-1; }else{ List<Integer> list = new ArrayList<>(); list.add(mid);
int index = mid + 1; while(index < arr.length){ if(arr[index] == val){ list.add(index); }else{ break; } index++; } index = mid-1; while(index >= 0){ if(arr[index] == val){ list.add(index); }else{ break; } index--; } return list; } } return null; }
public static int recursionInsertSearch01(int[] arr,int low,int high,int val){
if(val < arr[low] || val > arr[high] || low > high){ return -1; }
int mid = low + (high - low) * (val - arr[low])/(arr[high] - arr[low]);
if(val > arr[mid]){ return recursionInsertSearch01(arr, mid+1, high, val); }else if(val < arr[mid]){ return recursionInsertSearch01(arr, low, mid-1, val); }else{ return mid; } }
public static List<Integer> recursionInsertSearch02(int[] arr,int low,int high,int val){
if(val < arr[low] || val > arr[high] || low > high){ return null; }
int mid = low + (high - low) * (val - arr[low])/(arr[high] - arr[low]);
if(val > arr[mid]){ return recursionInsertSearch02(arr, mid+1, high, val); }else if(val < arr[mid]){ return recursionInsertSearch02(arr, low, mid-1, val); }else{ List<Integer> list = new ArrayList<>(); list.add(mid);
int index = mid + 1; while(index < arr.length){ if(arr[index] == val){ list.add(index); }else{ break; } index++; } index = mid-1; while(index >= 0){ if(arr[index] == val){ list.add(index); }else{ break; } index--; } return list; } } }
|
斐波那契查找
概念
斐波那契查找也叫做黄金分割法查找。
斐波那契查找也是二分查找的一种提升算法,通过运用黄金比例的概念在数列中选择查找点进行查找,提高查找效率。同样地,斐波那契查找也属于一种有序查找算法。
原理
对于斐波那契数列:1、1、2、3、5、8、13、21、34、55、89……(也可以从0开始),前后两个数字的比值随着数列的增加,越来越接近黄金比值0.618。
比如元素个数为89的有序列表。89在斐波那契数列中是34和55相加所得。
把元素个数为89的有序列表分成:前55个数据元素组成的前半段和34个数据元素组成的后半段。那么前半段元素个数和整个有序表长度的比值接近黄金比值0.618,而前后两段长度的比值也接近黄金比值0.618。
假如要查找的元素在前半段,那么继续按照斐波那契数列来看,55 = 34 + 21,所以继续把前半段分成前34个数据元素的前半段和后21个元素的后半段,继续查找,如此反复,直到查找成功或失败。这样斐波那契数列就被应用到查找算法中了。
总长度=f[k],
前半段长度=f[k-1],后半段长度=f[k-2]
有序列表的元素个数不是斐波那契数列中的数字时该如何处理呢?
当有序表的元素个数不是斐波那契数列中的某个数字时,需要把有序列表的长度补齐,让它成为斐波那契数列中的一个数值。
如果不是补齐,而是将多余的截掉是否可行?把原有序列表截断肯定是不可行的,因为可能把要查找的目标值截掉。
每次取斐波那契数列中的某个值时(f[k]),都会进行-1操作,这是因为数组下标从0开始。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
| public class Test01 { public static void main(String[] args) { int[] arr = {1,13,25,37,49,51,62,68,70,80,80}; List<Integer> fiboSearchList = fiboSearchList(arr, 80); System.out.println(Arrays.toString(fiboSearchList.toArray())); } public static List<Integer> fiboSearchList(int[] arr, int val) { int low = 0; int high = arr.length-1; int[] fiboArray = getFiboArray(10); int k = 0; while(arr.length > fiboArray[k]){ k++; } System.out.println("k = " + k); System.out.println("fiboArray = " + Arrays.toString(fiboArray)); int[] temp = Arrays.copyOf(arr, fiboArray[k]); System.out.println("temp=" + Arrays.toString(temp)); for (int i = high+1; i < temp.length; i++) { temp[i] = arr[high]; } System.out.println("补充数值的temp=" + Arrays.toString(temp)); while(low <= high){ int mid = low + fiboArray[k-1] - 1; if(val < temp[mid]){ high = mid-1;
k-=1; }else if(val > temp[mid]){ low = mid+1;
k -= 2; }else{ ArrayList<Integer> list = new ArrayList<>(); list.add(mid); int index = mid+1; while(index < arr.length){ if(arr[index] == val){ list.add(index); index++; }else{ break; } } index = mid-1; while(index > 0){ if(arr[index] == val){ list.add(index); index--; }else{ break; } } return list; } } return null; } public static int[] getFiboArray(int maxSize){ int[] fiboArray = new int[maxSize]; fiboArray[0] = 1; fiboArray[1] = 1; for (int i = 2; i < fiboArray.length; i++) { fiboArray[i] = fiboArray[i-1] + fiboArray[i-2]; } return fiboArray; } }
|