在这一周,我学习了在数组中求最值,求和(基础和升级),打乱数组的数据,反转数组,查找(其中分基本查找,二分查找,分块查找,哈希查找),还学习了排序(冒泡排序和选择排序),除此之外,我还练习了题目,结合三大结构,函数,数组,包括下列查找,排序
首先先回顾一下,数组是一个什么东西。数组是一种容器,可以用来存储同个数据类型的多个值。
求最值:
1.需要定义数组,
2.定义变量max:max的默认值一定是数组中存在的数据,一般将索引0当作默认值,
3.然后利用循环遍历数组,遍历数组得到的每一个元素,与max比较。
//1.定义数组 int num[] = { 33,5,22,44,55 }; int len = sizeof(num) / sizeof(int); //2.定义变量表示max int max = num[0]; //3.利用循环遍历数组,遍历数组得到每一个元素,拿着遍历得到的元素与max比较 for (int i = 1;i < len;i++) { if (num[i] > max) { max = num[i]; } } //4.输出打印 printf("%d\n", max);求和:
求和分为基础和升级,二者的区别在于,基础版可以数据重复,但是升级版不可以,所以应保证每一个元素只出现一次。
无论是基础还是升级,我们都用到随机数。如何得到随机数,我们应设置种子,再生成随机数。
既然升级版数据不可以重复,那在代码上二者的区别;升级多了一步判断随机数存在数组中之前应判断,刚生成的随机数是否已存在于数组中,我们可以利用函数判断随机数是否在数组中存在。
这里的代码只展示判断num与数组中的数据是否重复
//设置种子,生成10个 1到10 之间的随机数 srand(time(NULL)); //生成随机数 此时for后面的i++在下面,这意味着,循环变量i不会每次循环都自动加1 for (int i = 0;i < len;) //i=0时给arr[0]-arr[9]赋值 { int num = rand() % 10+1 ; //把随机数num存入数组中,但存入之前需要判断,判断刚刚生成的随机数是否已经存在于当前的数组中 int flag = contains(arr, len, num); if (!flag) //!flag: 如果flag为假,则flag==0,也就是不存在的意思 { arr[i] = num; //把目前num存入数组第i个位置 i++; //只有符合if里面的语句,才会加1 }//判断num在函数中是否存在 //存在,返回1 //不存在,返回0 int contains(int arr[],int len,int num) { for (int i = 0;i < len;i++) //i:依次表示数组中每一个索引 { //arr[i];依次表示数组中每一个数据 if (arr[i]==num) { return 1; } } return 0; }打乱数组中的数据;
核心原理是:遍历数组,将当前遍历到的元素与数组中随机位置的一个元素进行交换。
其中int index = rand() % len;作用是生成随机下标。rand() 会生成一个很大的随机数,通过 % len(取余数),可以将这个随机数限制在 0 到 len-1 的范围内(即 0 到 4),得到的就是一个随机的数组下标。
//1.定义数组 int arr[] = { 1,2,3,4,5, }; int len = sizeof(arr) / sizeof(int); //2.遍历数组(用于打乱数据),得到数组每一个元素,让这个元素跟随机索引进行交换 //设置种子 srand(time(NULL)); for (int i = 0;i < len;i++) { //获取一个随机索引(随机数) //min;0 max;4 int index = rand() % len; //rand是一个函数,作用是生成一个很大的随机数,所以我们要控制随机数的范围, //拿着i指向的元素,跟index指向的元素进行交换 int temp = arr[i]; arr[i] = arr[index]; arr[index] = temp; } //3.遍历数组(用于打印输出)省略...反转数组;
核心原理是:双指针法(首尾指针向中间靠拢)。在反转数组中用到的循环结构是while。其中while (i < j),括号中是条件判断语句(循环条件),只要左指针还在右指针的左边,就说明还没有相遇,需要继续交换。一旦 i >= j(相遇或者交叉),说明整个数组已经反转完毕,循环结束。
如何判断用for还是while:
1.已知循环次数或循环范围,用 for 循环更清晰。
2. 如果是不知道循环次数或循环范围,只知道结束条件,用 while 循环更合适。
//4.反转数组 int i = 0; int j = len-1; while (i < j) //用for循环过于繁琐,即使我们知道需要循环多少次(len/2次), //但这种“左右两边同时移动”的逻辑中,什么时候停取决于两边的相遇,而不是单纯的要循环多少次 //并且如何选择for还是while: //1.当知道循环次数或范围用for; //2.不知道循环次数或范围用while, / 而循环的终止条件依赖于多个变量动态变化时,while比for更适合 { int temp = num[i]; num[i] = num[j]; num[j] = temp; i++; // 双指针向中间靠拢,如果不打这两步,i和j不懂,变成死循环,还是在同样的位置互换 j--; } //5.遍历数组 printArr(num, len);查找:
基本查找,二分查找,插值查找,分块查找,哈希查找,
①基本查找(顺序查找):从头到尾遍历数组。
核心思路:从数组0索引开始依次往后查找。
如果找到了,就会返回数组中的索引。
如果没找到,就会返回-1。
//1.定义数组 int arr[] = { 23,52,34,89,16 }; int len = sizeof(arr) / sizeof(int); //2.定义一个变量表示要查找的数据 int num = 34; //3.调用函数查找数据 int index=order(arr, len, num); //4.输出索引 printf("%d\n", index); return 0; } //函数作用:查找数组中的数据 //返回值;数据所在的索引, int order(int arr[], int len, int num) { for(int i = 0;i < len;i++) { if (arr[i] == num) { return i; } } return -1; }②二分查找(折半查找):
使用前提条件:
数组中的数据必须是有序,可以是从小到大也可以是哦从大到小,就是不可以是乱的。
核心思路:每次排除一半的查找范围
1.定义min和max,表示当前的查找范围
2.定义mid,mid在min和max中间,mid=(min+max)/2
3.如果要查找的数据在mid左边,则min不变,max=mid-1
如果要查找的数据在mid右边,则max不变,min=mid+1
//1.定义数组 int arr[] = { 12,25,37,58,60,67,82,92 }; int len = sizeof(arr) / sizeof(int); //2.定义一个变量表示要查找的数据 int num = 82; //3.调用函数查找数据 int index = binaryserach(arr, len, num); //4.输出索引 printf("%d\n", index); return 0; } //函数作用;利用二分查找查找数据 //返回值:数据在数组中的索引 //找到了, 就会返回数据对应的索引 //没找到,就会返回-1 int binaryserach(int arr[], int len, int num) { //1.确定查找范围 int min = 0; int max = len - 1; //2.利用循环不断进行查找 while (min <= max) { //确定中间位置 int mid = (min+max)/ 2; //进行比较,此时min、mid、max都表示数组的索引 //而此时的num表示要查找的元素,所以一定要拿arr[mid]跟num比较 //判断 if (arr[mid] < num) //要查找的num在右边,则max不变,min为mid+1 { min = mid + 1; } else if (arr[mid] > num) //要查找的num在左边,则min不变,max为mid-1 { max = mid - 1; } else { return mid; } //3.如果min大于max了,表示数据不存在,返回-1 return -1; }③插值查找:
使用前提条件:
数据有序,且数据分布尽可能均匀一点
它与二分查找操作差不多,只要把mid的表达式换一下就好了
mid=min+(key-arr【min】)/(arr【max】-arr【min】)*(max-min)
它对于二分查找的优势:效率比二分查找快,但如果前提条件不符,还会适得其反
代码不展示,与二分查找一样,只需把mid的表达式换一下即可
//确定中间位置 int mid = min + (key - arr[min]) / (arr[max] - arr[min]) * (max - min); //可以直接用num,没必要多余定义key
④分块查找:
核心思路:先确定要查找的元素在哪一块,然后在其块内挨个查找。
如何分块,分块原则:
• 块内无序:每一个小块内部的数据,可以是乱序的(不需要排序)。
• 块间有序:第 1 块中的最大值,必须小于第 2 块中的最小值;第 2 块的最大值,必须小于第 3 块的最小值……以此类推。(即整体趋势是递增的)。
一般块数的数量等于数据个数的开根号(eg:16个数据一般分为4块)
且需要注意,分块中每一块的数据范围不可以有交集
⑤哈希查找:
核心思路:把数据存入时,通过公式计算出一个位置(哈希值);查找时,用同样的公式算出位置,直接去那个位置取数据。
它和另外四个查找不一样,它不找数据,而是直接算出数据的位置。
排序算法:
冒泡排序,选择排序
①冒泡排序:
核心思路:1.相邻的数据两两比较,小的放前面,大的放后面。(大的放右,小的放左)
2.第一轮循环结束,最大的已经在最右边,放在数组最右边。
3.剩余数据继续循环排序。
如果数组中有n个数据,总共我们只要执行 n-1 轮代码即可。
//2.利用冒泡排序,将数组中的数据进行升序排序 //我们可以发现四轮下来,只有条件判断语句那一部分变动了,所以我们可以在套一个循环,使它变得简便 一; for (int i = 0;i < len -1;i++) // i:范围0,1,2,3, 此时条件判断语句要写len-1,防止索引越界 //外循环:控制比较的轮数 //随着i的增加,内循环范围越来越小 { //内循环:控制每一轮的次数 //j:表示进行比较的元素下标 //每过一轮,就会有一个最大的数排到末尾,所以后面的数就不用比较了则-i for (int j = 0;j < len - 1-i;j++) { //相邻元素二二比较,小的在前,大的在后 //j与j+1比较 if (arr[j] > arr[j + 1]) { int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } }利用冒泡排序,解决方法:双重循环
双重循环本质:指内循环重复执行n次。
内循环:找到本次循环的较大值,再把较大值放到最右边。
外循环:把这个动作执行n次。
②选择排序:
核心思路:
1.索引0开始,拿着每一个索引上的元素跟后面元素依次比较,小的放前面,大的放后面。
2.第一轮循环结束,最小值已经找到,放在数组最左边
3.第二轮循环从索引1开始依次比较。依次类推…
//2.利用选择排序,将数组中的数据进行升序排序 //第一轮: i=0,j=1,2,3,4, // 第二轮,i=1,j=2,3,4, // 第三轮,i=2,j=3,4, // 第四轮,i=3,j=4, //i;依次表示数组中每一个索引 for (int i = 0;i < len - 1;i++) //i<len-1,防止索引越界 { //j;依次表示i索引后面的每一个索引 for (int j = i + 1;j < len;j++) //假如第一轮比较结束,它会直接将比索引0小的互换位置 { //判断的代码逻辑是: 拿着 arr[i] 去和后面所有元素相比,只要遇到比它小的,当场就换, 一轮下来,最小的那个必定会通过多次交换,“顶”到 i 的位置上。 if (arr[i] > arr[j]) //如果成立,则互换位置 { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } }冒泡排序VS选择排序
核心动作:相邻两两比较,频繁交换 找最小/最大值,一轮只换一次
适用场景:数据量极少 数据量极少
算术练习
回文数
斐波那契数列
水仙花数
综合练习
1.
2.
3.