一、二分查询
1.概念
二分查询又被称为二分查找,是一种在有序数组或序列中快速查找到对应元素的一种方法。每次查找范围缩小至原来的一半。
①前提条件
数组和列表必须有序,否则无法进行二分查找。
②初始化
确定查找数组和列表的左边界(通常为数组或列表第一个元素)和右边界(通常为数组和列表最后一个元素)。
③循环
- 计算中间元素的序列
- 比较中间元素和目标元素
- 如果中间元素等于目标元素,那么查询结束,返回中间元素的索引。
- 如果目标元素大于中间元素,说明目标元素在中间元素的右半部分,那么左边界移到中间元素的右边。
- 如果目标元素小于中间元素,说明目标元素在中间元素的左半部分,那么右边界移至中间元素的左边。
④结束条件
如果查找到目标元素的索引,退出。
或者如果左边元素的索引大于右边元素,说明目标元素不在数组内,那么退出。
2.代码
int BinaryFindValue(const int* arr, int n, int value)
{int pos = -1;if (n < 1 || arr == NULL) return pos;int left = 0;int right = n-1;while (left<=right){int mid = (right - left +1 ) / 2 + left;if (value < arr[mid]){right = mid - 1;}else if (value > arr[mid]){left = mid + 1;}else{pos = mid;break;}} return pos;
}
int main()
{const int n = 5;int arr[n] = { 10,11,12,13,14 };int val = 0;scanf("%d", &val);int m = BinaryFindValue(arr, n, val);printf("%d", m);
}
也可以将这一行写成这样
int mid = (right - left +1 ) / 2 + left;
修改后:
int mid = (right - left + 1) >> 1 + left;
>>
是右移位操作符,在计算机中执行二进制位的右移。>> 1
表示将二进制位向右移动一位,相当于除以2。
注意这里有个问题:
int mid =((right - left + 1) >> 1) + left;
加号大于右移操作符,这样写的话是1+left然后右移,所以要给前面加上括号。
最终正确代码
int BinaryFindValue(const int* arr, int n, int value)
{int pos = -1;if (n < 1 || arr == NULL) return pos;int left = 0;int right = n-1;while (left<=right){int mid = ((right - left + 1) >> 1) + left;if (value < arr[mid]){right = mid - 1;}else if (value > arr[mid]){left = mid + 1;}else{pos = mid;break;}} return pos;
}
int main()
{const int n = 5;int arr[n] = { 10,11,12,13,14 };int val = 0;scanf("%d", &val);int m = BinaryFindValue(arr, n, val);printf("%d", m);
}
3. Q&A
Q:下面这个代码有问题,请详细说明原因
A:
问题一:是left<right判断条件,如果目标值是右边界值的话没有办法正确输出值。具体看下面的图示。
问题二:是使用(left+right)/2,如果left和right足够大,他们的mid很可能超出最大整数的范围,容易溢出。
修改:
① 修改判断条件,让left<=right的时候退出循环,这样就可以查找到边界的目标元素。
②修改查找中间元素的计算方式,所以使用下面的方式只对区域进行取半。
4.优化一
如果我的序列中有多个重复的数字,我想要找到最左端数字的下标,你需要怎么做?
添加一个判断
while (arr[mid - 1] == value){mid--;}pos = mid;break;
判断arr[mid]和value相等的时候,它的前一个值是否也相等,但是这样写是错误的,因为如图,在下面的示例中,要找到12最左边元素的下标值,mid-1判断到mid=0下标的时候,再-1会造成越界,所以我们要对于判断条件增加。
增加一个条件mid-1>=0,防止越界行为,老师在这里增加的条件是mid>left,也是相同的道理,两种写法都可以。
//while ((mid>left) && (arr[mid - 1] == value))
while ((mid-1 >= 0) && (arr[mid - 1] == value)){mid--;}pos = mid;break;
int BinaryFindValue(const int* arr, int n, int value)
{int pos = -1;if (n < 1 || arr == NULL) return pos;int left = 0;int right = n-1;while (left<=right){int mid =((right - left + 1) >> 1) + left;if (value < arr[mid]){right = mid - 1;}else if (value > arr[mid]){left = mid + 1;}else{while ((mid-1 >= 0) && (arr[mid - 1] == value)){mid--;}pos = mid;break;}} return pos;
}
int main()
{const int n = 13;int arr[n] = { 12,12,12,13,13,13,13,13,24,24,24,25,26};int val = 0;scanf("%d", &val);int m = BinaryFindValue(arr, n, val);printf("%d", m);
}
5.优化二
在这里查找最左端元素的时候,你使用的是递归查询,请问怎么样使用二分法继续进行最左端元素的查找。
使用递归方法继续查找呢?
暂无思路,待补充……
二、结构体
1.定义
结构体是由我们自己设计的一种类型。
结构体和数组的区别:数组中所有元素的类型一致,但是结构体中元素的类型不需要一致。
结构体的定义
struct Student //结构体名
{
//成员列表
};
这里结束的时候记得写分号哦!
注意: 在C语言中,这里的Student是结构体名,struct Student是结构体类型名。在进行创建结构体变量时,需要使用结构体类型名创建。但是在C++中结构体类型名和结构体名没有区别。
Student sx;//错误
struct Student sx;//正确
2.初始化
结构体的在声明的时候不需要初始化,因为它此时还是类型,不会开辟空间,所以不能赋初值。在定义结构体变量时,系统才会给结构体分配空间。
初始化顺序需要与结构体声明次序保持一致。
①使用花括号初始化
struct Student
{char s_name[20];int age;
};
int main()
{struct Student sx = { "lizeyu",23 };printf("%s %d\n", sx.s_name, sx.age);
}
结构体嵌套结构体初始化,也是使用花括号初始化。
struct Date
{int year;int month;int day;
};
struct Student
{char s_name[20];Date birthday;int age;
};
int main()
{struct Student sx = { "lizeyu",{2000,8,21},23 };printf("%s %d\n", sx.s_name, sx.age);
}
或者写成这个样子也是可以的:
struct Student sx = { "lizeyu",2000,8,21,23 };
3.结构体在内存中的存储
如果结构体内的数组是按照[ ]方式声明的,定义变量的时候,栈区为数组分配对应字节的空间。
如果声明结构体成员列表的时候使用指针的方式声明数组,那么栈区的指针指向被存储在.data区的数组。