上一章,我们多次提到二分插入排序
,这是一个用二分法来找插入位置的插入排序算法。
但其实,我们最早一次提到二分法,是在《经典机器学习及其Python实现:9.决策树》 ,讨论信息熵的时候。
二分法的时间复杂度
当时,我们举了这么一个例子。
假设,现在有8个西瓜,而且8个西瓜中只有一个是好瓜。而我们对西瓜完全不了解,只能猜。这时候卖西瓜的和我们玩一个游戏,猜一次一块钱。
那么,成本最小的方式是:
graph LR
1-4{在1-4中?}
1-2{在1-2中?}
5-6{在5-6中?}
1{1?}
3{3?}
5{5?}
7{7?}
g1(瓜-1)
g2(瓜-2)
g3(瓜-3)
g4(瓜-4)
g5(瓜-5)
g6(瓜-6)
g7(瓜-7)
g8(瓜-8)
1-4 -- 是 --> 1-2
1-4 -- 否 --> 5-6
1-2 -- 是 --> 1
1-2 -- 否 --> 3
5-6 -- 是 --> 5
5-6 -- 否 --> 7
1 -- 是 --> g1
1 -- 否 --> g2
3 -- 是 --> g3
3 -- 否 --> g4
5 -- 是 --> g5
5 -- 否 --> g6
7 -- 是 --> g7
7 -- 否 --> g8
按照这个策略,我们只需要猜三次,就可以选中最好的西瓜。即我们只需要猜三次,就可以让不确定变成确定。
现在,我们把西瓜的数量改一下。
现在有16个西瓜,需要多少猜多少次?很简单,4次。
那么32个西瓜呢?5次。
被查找区间的大小变化:
n , n 2 , n 4 , 8 8 , ⋯ , n 2 k , ⋯ n,\frac{n}{2},\frac{n}{4},\frac{8}{8},\cdots,\frac{n}{2^k},\cdots
n , 2 n , 4 n , 8 8 , ⋯ , 2 k n , ⋯
现在问,有n个西瓜,需要猜多少次?
O ( log n ) O(\log n)
O ( log n )
这就是二分法的时间复杂度,极其高效。
二分法的局限性
二分法依赖的数据结构是数组。
链表不可以,因为二分法要频繁的根据下标去访问元素,而链表需要用一个一个的指针去指,时间复杂度是O ( n ) O(n) O ( n ) ,有指过去的时间,不如直接遍历。
但是,数组的话可以做到时间复杂度是O ( 1 ) O(1) O ( 1 ) 。
二分法针对的是有序数据。
因为每次选择一段都需要一个反馈,才能做下一步的二分操作。(买西瓜的例子,可以看成是按照特殊排序方法排序的有序数据)
数据量太大的话,不适合二分法。
这个或许不太好理解,毕竟还有这么一个式子成立。lim n → ∞ log n n = 0 \lim_{n \rightarrow \infty} \frac{\log n}{n} = 0 lim n → ∞ n l o g n = 0
理论上应该数据量越大越适合二分法。但,二分法依赖于数组,数组要求是内存中的连续空间,如果我们的数据有1GB,那就要求1GB的连续的内存空间。所以数据量太大,不适合二分法。
二分法的应用
接下来我们来讨论二分法的5个常见应用。
查找是否存在等于给定值的元素
查找等于给定值的第一个位置
查找等于给定值的最后一个位置
查找大于等于给定值的第一个位置
查找小于等于给定值的最后一个位置
查找是否存在等于给定值的元素
如果中间值等于给定值,说明存在。
如果中间值大于给定值,说明在左边,rIndex
左移。
如果中间值小于给定值,说明在右边,lIndex
右移。
示例代码:
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 package ch06;public class BinarySearchExist { public static boolean binarySearchExist (int [] arr,int target) { int lIndex = 0 ; int rIndex = arr.length - 1 ; int midIndex = 0 ; while (lIndex < rIndex){ midIndex = lIndex + (rIndex - lIndex) / 2 ; if (arr[midIndex] == target){ return true ; }else if (arr[midIndex] > target){ rIndex = midIndex - 1 ; }else { lIndex = midIndex + 1 ; } } return (arr[lIndex] == target); } }
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 def binarySearchExist (arr, target) : lIndex = 0 rIndex = len(arr) - 1 midIndex = 0 while lIndex < rIndex: midIndex = lIndex + (rIndex - lIndex) // 2 if arr[midIndex] == target: return True elif arr[midIndex] > target: rIndex = midIndex - 1 else : lIndex = midIndex + 1 return arr[lIndex] == target
查找等于给定值的第一个位置
第二个问题,我们加大难度了。等于给定值的第一个位置。
如果等于给定值的话,为了找第一个位置,我们还需要rIndex
再左移,但是有可能左移就移出去了,所以我们再每一次相等的时候,并在左移之前,都把当前位置记下来。如果真移出去了,那么当前位置就是等于给定值的第一个位置。
示例代码:
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 package ch06;public class BinarySearchFirstE { public static int binarySearchFirstE (int [] arr,int target) { int index = -1 ; int lIndex = 0 ; int rIndex = arr.length - 1 ; int midIndex = 0 ; while (lIndex <= rIndex){ midIndex = lIndex + (rIndex - lIndex) / 2 ; if (arr[midIndex] == target){ index = midIndex; rIndex = midIndex-1 ; }else if (arr[midIndex] > target){ rIndex = midIndex - 1 ; }else { lIndex = midIndex + 1 ; } } return index; } }
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 def binarySearchFirstE (arr, target) : index = -1 lIndex = 0 rIndex = len(arr) - 1 midIndex = 0 while lIndex <= rIndex: midIndex = lIndex + (rIndex - lIndex) // 2 if arr[midIndex] == target: index = midIndex rIndex = midIndex - 1 elif arr[midIndex] > target: rIndex = midIndex - 1 else : lIndex = midIndex + 1 return index
查找等于给定值的最后一个位置
这个问题和查找等于给定值的第一个位置
差不多。
如果等于给定值的话,为了找第一个位置,我们需要lIndex
右移。
示例代码:
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 package ch06;public class BinarySearchLastE { public static int binarySearchLastE (int [] arr,int target) { int index = -1 ; int lIndex = 0 ; int rIndex = arr.length - 1 ; int midIndex = 0 ; while (lIndex <= rIndex){ midIndex = lIndex + (rIndex - lIndex) / 2 ; if (arr[midIndex] == target){ index = midIndex; lIndex = midIndex + 1 ; }else if (arr[midIndex] > target){ rIndex = midIndex - 1 ; }else { lIndex = midIndex + 1 ; } } return index; } }
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 def binarySearchLastE (arr, target) : index = -1 lIndex = 0 rIndex = len(arr) - 1 midIndex = 0 while lIndex <= rIndex: midIndex = lIndex + (rIndex - lIndex) // 2 if arr[midIndex] == target: index = midIndex lIndex = midIndex + 1 elif arr[midIndex] > target: rIndex = midIndex - 1 else : lIndex = midIndex + 1 return index
查找大于等于给定值的第一个位置
这个问题和查找等于给定值的第一个位置
差不多。
大于等于给定值的情况下,我们都让rIndex
左移。
示例代码:
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 package ch06;public class BinarySearchFirstGE { public static int binarySearchFirstGE (int [] arr,int target) { int index = -1 ; int lIndex = 0 ; int rIndex = arr.length - 1 ; int midIndex = 0 ; while (lIndex <= rIndex){ midIndex = lIndex + (rIndex - lIndex) / 2 ; if (arr[midIndex] >= target){ index = midIndex; rIndex = midIndex - 1 ; }else { lIndex = midIndex + 1 ; } } return index; } }
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 def binarySearchFirstGE (arr, target) : index = -1 lIndex = 0 rIndex = len(arr) - 1 midIndex = 0 while lIndex <= rIndex: midIndex = lIndex + (rIndex - lIndex) // 2 if arr[midIndex] >= target: index = midIndex rIndex = midIndex - 1 else : lIndex = midIndex + 1 return index
查找小于等于给定值的最后一个位置
这个问题和查找等于给定值的最后一个位置
差不多。
大于等于给定值的情况下,我们都让lIndex
右移。
示例代码:
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 package ch06;public class BinarySearchLastLE { public static int binarySearchLastLE (int [] arr,int target) { int index = -1 ; int lIndex = 0 ; int rIndex = arr.length - 1 ; int midIndex = 0 ; while (lIndex <= rIndex){ midIndex = lIndex + (rIndex - lIndex) / 2 ; if (arr[midIndex] <= target){ index = midIndex; lIndex = midIndex + 1 ; }else { rIndex = midIndex - 1 ; } } return index; } }
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 def binarySearchLastLE (arr, target) : index = -1 lIndex = 0 rIndex = len(arr) - 1 midIndex = 0 while lIndex <= rIndex: midIndex = lIndex + (rIndex - lIndex) // 2 if arr[midIndex] <= target: index = midIndex lIndex = midIndex + 1 elif arr[midIndex] > target: rIndex = midIndex - 1 return index
对数器
示例代码:
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 package ch06;import java.util.ArrayList;import java.util.Arrays;public class Comparator_BinarySearch { public static void main (String[] args) throws Exception { int testTimes = 500000 ; int maxSize = 100 ; int maxValue = 100 ; boolean isSuccess = true ; for (int i = 0 ; i < testTimes; i++) { int [] arr = genRandomArr(maxSize,maxValue); if (null == arr || arr.length < 1 ){ continue ; } Arrays.sort(arr); int target = (int )(Math.random() * maxValue); ArrayList<Integer> aList = new ArrayList<Integer>(); boolean a = searchExist(arr,target); aList.add(a?1 :0 ); aList.add(searchFirstE(arr,target)); aList.add(searchLastE(arr,target)); aList.add(searchFirstGE(arr,target)); aList.add(searchLastLE(arr,target)); ArrayList<Integer> bList = new ArrayList<Integer>(); boolean b = BinarySearchExist.binarySearchExist(arr,target); bList.add(b?1 :0 ); bList.add(BinarySearchFirstE.binarySearchFirstE(arr,target)); bList.add(BinarySearchLastE.binarySearchLastE(arr,target)); bList.add(BinarySearchFirstGE.binarySearchFirstGE(arr,target)); bList.add(BinarySearchLastLE.binarySearchLastLE(arr,target)); if (!isEqual(aList,bList)){ System.out.println(Arrays.toString(arr)); System.out.println(target); System.out.println(aList); System.out.println(bList); isSuccess = false ; break ; } } System.out.print(isSuccess); } public static boolean isEqual (ArrayList<Integer> a,ArrayList<Integer> b) { for (int i = 0 ; i < a.size(); i++) { if (a.get(i) != b.get(i)){ return false ; } } return true ; } public static int [] genRandomArr(int maxSize,int maxValue){ int [] arr = new int [(int )(Math.random() * maxSize)]; for (int i = 0 ; i < arr.length; i++) { arr[i] = (int )(Math.random() * maxValue); } return arr; } public static boolean searchExist (int [] arr,int target) { boolean isExist = false ; for (int i = 0 ; i < arr.length; i++) { if (arr[i] == target){ isExist = true ; break ; } } return isExist; } public static int searchFirstE (int [] arr,int target) { int index = -1 ; for (int i = 0 ; i < arr.length; i++) { if (arr[i] == target){ index = i; break ; } } return index; } public static int searchLastE (int [] arr,int target) { int index = -1 ; boolean getEqual = false ; for (int i = 0 ; i < arr.length; i++) { if (arr[i] == target){ getEqual = true ; } if (getEqual == true ){ if (i == arr.length - 1 ){ index = i; break ; }else { if (arr[i + 1 ] > target){ index = i; break ; } } } } return index; } public static int searchFirstGE (int [] arr,int target) { int index = -1 ; for (int i = 0 ; i < arr.length; i++) { if (arr[i] >= target){ index = i; break ; } } return index; } public static int searchLastLE (int [] arr,int target) { int index = -1 ; for (int i = 0 ; i < arr.length; i++) { if (arr[i] > target){ index = i-1 ; break ; } if (i == arr.length - 1 ){ index = i; } } return index; } }
运行结果:
示例代码:
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 import randomfrom ch06 import BinarySearchExist, BinarySearchFirstE, BinarySearchLastE, BinarySearchFirstGE, BinarySearchLastLEdef gen_random_arr (max_size, max_value) : """ 生成一个随机的数组 :param max_size: 生成的随机数组的最大长度 :param max_value: 生成的随机数组的最大值 :return: 生成的随机数组 """ arr_rnt = [] random_size = random.randint(0 , max_size) for i in range(random_size): arr_rnt.append(random.randint(0 , max_value)) return arr_rnt def search_exist (arr, target) : is_exist = False for iter in arr: if iter == target: is_exist = True break return is_exist def search_first_e (arr, target) : index = -1 for i in range(len(arr)): if arr[i] == target: index = i break return index def search_last_e (arr, target) : index = -1 get_equal = False for i in range(len(arr)): if arr[i] == target: get_equal = True if get_equal: if i == (len(arr) - 1 ): index = i break elif arr[i + 1 ] > target: index = i break return index def search_first_ge (arr, target) : index = -1 for i in range(len(arr)): if arr[i] >= target: index = i break return index def search_last_le (arr, target) : index = -1 for i in range(len(arr)): if arr[i] > target: index = i - 1 break if i == (len(arr) - 1 ): index = i return index if __name__ == '__main__' : testTimes = 500000 maxSize = 100 maxValue = 100 is_success = True for i in range(testTimes): arr = gen_random_arr(maxSize, maxValue) if arr is None or len(arr) < 1 : continue arr.sort() target = random.randint(0 , maxValue) aList = [] a = search_exist(arr, target) aList.append(1 if a else 0 ) aList.append(search_first_e(arr, target)) aList.append(search_last_e(arr, target)) aList.append(search_first_ge(arr, target)) aList.append(search_last_le(arr, target)) bList = [] b = BinarySearchExist.binarySearchExist(arr, target) bList.append(1 if b else 0 ) bList.append(BinarySearchFirstE.binarySearchFirstE(arr, target)) bList.append(BinarySearchLastE.binarySearchLastE(arr, target)) bList.append(BinarySearchFirstGE.binarySearchFirstGE(arr, target)) bList.append(BinarySearchLastLE.binarySearchLastLE(arr, target)) if aList != bList: print(arr) print(target) print(aList) print(bList) is_success = False break print(is_success)
运行结果:
二分插入排序
最后再回到二分插入排序
。应该插入到哪个位置?
小于等于给定值的最后一个位置的再下一个位置。
示例代码:
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 package ch06;import java.util.Arrays;public class BinaryInsertionSort { public static void main (String[] args) { int [] arr = {3 ,44 ,38 ,5 ,47 ,15 ,36 ,26 ,27 ,2 ,46 ,4 ,19 ,50 ,48 }; System.out.println(Arrays.toString(arr)); if (null != arr && arr.length > 1 ){ arr = binaryInsertionSort(arr); } System.out.println(Arrays.toString(arr)); } public static int [] binaryInsertionSort(int [] arr){ for (int i = 1 ; i < arr.length; i++) { int key = arr[i]; int index = binarySearchLastLE(arr, key, 0 , i - 1 ) + 1 ; for (int j = i; j > index; j--) { arr[j] = arr[j - 1 ]; } arr[index] = key; } return arr; } private static int binarySearchLastLE (int [] sortedArr, int num, int from, int to) { int index = -1 ; int lIndex = from; int rIndex = to; int midIndex = 0 ; while (lIndex <= rIndex){ midIndex = lIndex + (rIndex - lIndex) / 2 ; if (sortedArr[midIndex] <= num){ index = midIndex; lIndex = midIndex + 1 ; }else { rIndex = midIndex - 1 ; } } return index; } public static void swap (int [] arr,int i,int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } }
运行结果:
1 2 [3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48] [2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]
示例代码:
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 def binaryInsertionSort (arr_args) : """ 二分插入排序 :param arr_args: 需要排序的数组 :return: 排序之后的数组 """ for i in range(len(arr_args)): key = arr_args[i] index = binarySearchLastLE(arr_args, key, 0 , i - 1 ) + 1 ; for j in range(i, index, -1 ): arr_args[j] = arr_args[j - 1 ] arr_args[index] = key return arr_args def binarySearchLastLE (sortedArr, num, fromIndex, toIndex) : index = -1 lIndex = fromIndex rIndex = toIndex while lIndex <= rIndex: midIndex = lIndex + (rIndex - lIndex) // 2 if sortedArr[midIndex] <= num: index = midIndex lIndex = midIndex + 1 elif sortedArr[midIndex] > num: rIndex = midIndex - 1 return index if __name__ == '__main__' : arr = [3 , 44 , 38 , 5 , 47 , 15 , 36 , 26 , 27 , 2 , 46 , 4 , 19 , 50 , 48 ] print(arr) print(binaryInsertionSort(arr))
运行结果:
1 2 [3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48] [2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]
版权声明: 本博客所有文章版权为文章作者所有,未经书面许可,任何机构和个人不得以任何形式转载、摘编或复制。