二分查找的二分法和黄金分割点比较
笔记和代码的思路来源:
好大学慕课浙江大学陈越、何钦铭的《数据结构》
讨论3.1 黄金分割查找?
老师参与
在二分查找中,我们是取mid等于left和right的中间值,即用等分的方法进行查找。
那为什么一定要等分呐?能不能进行“黄金分割”?也就是mid=left+0.618(right-left),当然mid要取整数。如果这样查找,时间复杂性是多少?也许你还可以编程做个试验,比较一下二分法和“黄金分割”法的执行效率。
证明来自好白的小白。
二分法每次能有100%的概率能只剩50%的数据,每次剩下的期望为50%,即每次除以2。所以时间复杂度是。
而黄金分割的话每次都有0.618的概率剩0.618,0.382的概率剩0.382,每次剩下的期望为0.528,即每次除以1.894。所以时间复杂度是。
虽然两者都是O(logN)类,但是系数不同,黄金分割法所需时间约为二分法的1.085倍(此处没考虑取整……)。
假设分割点距左侧的距离除以全长等于p,那么每次剩下的期望为个全长。要让这个期望最小,我们知道p要等于1-p。所以p=0.5。所以二分法分在正中。
下面是是自己的代码和测试过程:
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<time.h> 4 #include<math.h> 5 6 #define MAXSIZE 1000000 7 8 typedef int dataType; 9 10 typedef struct node{ 11 dataType data[MAXSIZE]; 12 int length; 13 }lis,*pList; 14 15 16 pList createEmptyList(){ 17 pList list = (pList)malloc(sizeof(lis)); 18 if(list){ 19 list->length=-1; 20 } 21 return list; 22 } 23 24 int isFull(pList list){ 25 return (list->length==MAXSIZE-1); 26 } 27 28 int isEmpty(pList list){ 29 return (list->length==-1); 30 } 31 32 void insert(pList list,dataType element){ 33 if(isFull(list)){ 34 printf("the list has full"); 35 return; 36 } 37 list->data[++(list->length)]=element; 38 39 } 40 41 dataType findIndex(pList list,int index){ 42 if(isEmpty(list)){ 43 printf("the list is empty"); 44 return; 45 } 46 return list->data[index]; 47 } 48 49 void toString(pList list){ 50 int length=0; 51 printf("\ntoString:"); 52 while(length<=list->length){ 53 printf("%d ",list->data[length]); 54 length++; 55 } 56 } 57 58 59 /* 60 不使用哨兵是算法就会导致每次循环进行一次i<=list->length的判断, 61 为了演示方便,我们这里的数组角标从1开始,0角标存放的是数组的长度+1 62 */ 63 int orderSearch(pList list,int element){ 64 int i; 65 for(i=0;i<=list->length && list->data[i]!=element;i++); 66 if(i>list->length){ 67 return -1; 68 }else{ 69 return i; 70 } 71 } 72 73 /* 74 使用顺序查找,使用哨兵算法, 75 如果使用哨兵算法,那么数组的第一个元素就只能存放数组的长度 76 数组的真正的角标是从1开始的。0号角标存放的需要查找的元素element, 77 当循环退出是,要么是找到了,返回找到元素的角标i 78 要么是i=0;没找到 79 */ 80 int orderSearch2(pList list,int element){ 81 int i; 82 list->data[0]=element; 83 for(i=list->length;list->data[i]!=element;i--); 84 return i;//0表示没有找到 85 } 86 87 /*二分查找,去终点作为分割点*/ 88 int binarySearch(pList list,int element){ 89 int left=1; 90 int right=list->length; 91 while(left<=right){ 92 int mid=(right+left)/2; 93 if(element>list->data[mid]){ 94 left=mid+1; 95 }else if(element<list->data[mid]){ 96 right = mid - 1; 97 }else{ 98 return mid; 99 } 100 } 101 return -1;//没找到,返回-1 102 } 103 104 /* 105 二分查找,取黄金分割点作为中点 106 */ 107 int binarySearch2(pList list,int element){ 108 int left=1; 109 int right=list->length; 110 while(left<=right){ 111 int mid=left+0.618*(right-left); 112 if(element>list->data[mid]){ 113 left=mid+1; 114 }else if(element<list->data[mid]){ 115 right = mid - 1; 116 }else{ 117 return mid; 118 } 119 } 120 return -1;//没找到,返回-1 121 } 122 123 124 void main(){ 125 int i=0,j=0; 126 clock_t start,end; 127 double duration;//used to stored top - end 128 129 int count =10; 130 pList list = createEmptyList(); 131 132 insert(list,MAXSIZE); 133 for(i=1;i<MAXSIZE;i++){ 134 insert(list,i); 135 } 136 137 138 139 start=clock(); 140 start=clock(); 141 for(j=0;j<count;j++){ 142 for(i=0;i<MAXSIZE;i++){ 143 binarySearch(list,i);//使用中点分割 144 } 145 } 146 end=clock(); 147 duration=((double)(end-start))/CLK_TCK/MAXSIZE/count; 148 printf("use middle as the split point:%f\n",duration); 149 150 151 start=clock(); 152 for(j=0;j<count;j++){ 153 for(i=0;i<MAXSIZE;i++){ 154 binarySearch2(list,i);//使用黄金分割点分割 155 } 156 } 157 end=clock(); 158 duration=((double)(end-start))/CLK_TCK/MAXSIZE/count; 159 printf("use huangJinFenGeDian as the split point:%f\n",duration); 160 161 162 //toString(list); 163 164 /* 165 insert(list,10); 166 for(i=1;i<=10;i++){ 167 insert(list,i); 168 } 169 printf("%d ",orderSearch2(list,7)); 170 printf("%d ",orderSearch2(list,11)); 171 printf("%d ",binarySearch(list,9)); 172 printf("%d ",binarySearch(list,11)); 173 toString(list); 174 */ 175 }
进行累计计时就求平均的思想,但是当count=100时,PC机运算时间太慢了,就没有测试。
下面是count=10的测试结果,发现基本上查不了多少。但是上面的逻辑证明更加准确。