C语言程序设计 | 2020级计科三班 |
---|---|
作业的要求 | 作业要求 |
作业的目标 | 正确使用一维数据,了解排列算法 |
参考文献 | C语言程序设计 |
学号 | 20209028 |
2.本周作业
2.1 完成PTA作业,并给出编程题完成截图
2.2 题目:快速寻找满足条件的两个数
能否快速找出一个数中的两个数字,让这两个数字之和等于一个给定的值,为了简化起见,我们假设这个数组中肯定存在至少一组符合要求的解。
要求:
1.根据三种解法给出相应的代码,并给出测试数据。
2.请说明三种算法的区别是什么?你还可以给出更好的算法吗?
-
解法一:采用穷举法,从数组中任意取出两个数字,计算两者之和是否为给定的数字。
-
解法二:对数组中的每个数字arr[i]都判别Sum-arr[i]是否在数组中。
-
错误解法1:
错因1:在输出测试数据时丢掉了一些数据,经过调试发现原因。以图中测试数据为例,我的结果丢掉了2 6和3 5;调试发现丢掉的这两组数据中的low大于high,从而跳出了循环,输不出结果,i=1循环开始时high和low还处于i=0的二分查找法缩小范围。在for循环中加入“low=arr[i],high=arr[n-1];”使得新的循环重新使用二分查找法。 -
错误解法2:
错因2:以"sum=8"为例,输入的数组中只有一个4时,输出的结果存在4+4=8; -
正确解法三:对数组进行排序,然后使用二分查找法针对arr[i]查找Sum-arr[i]。
-
三种算法的区别:
解法一(穷举法):遍历数组中的每一个数,将满足条件(两数相加等于sum)输出,运用了双for语句,其时间复杂度为N(N-1)/2即O(N^2);
解法二:与解法一有相似之处,遍历数组中的每一个数,将满足条件(sum减去一个数,判断另一个数是否在数组中)输出,运用了双for语句,其时间复杂度是O(Nlog2N);
解法三(二分查找法):先将数组中的数从小到大按顺序排列,再利用二分查找法找出目标数,其时间复杂度是O(2log2N);
区别:三种不同的解法,所用时间逐级递减,并且,解法一二存在一个弊端:当数组中的数很多时,解法三大大减少了所需的时间,使得程序更加完美。
注:二分查找法:(这里假设数组元素呈升序排列)将n个元素分成个数大致相同的两半,取a[n/2]与欲查找的x作比较,如果x=a[n/2]则找到x,算法终止;如 果x<a[n/2],则我们只要在数组a的左半部继续搜索x;如果x>a[n/2],则我们只要在数组a的右 半部继续搜索x。
public static int Method(int[] nums, int low, int high, int target)
{
while (low <= high)
{
int middle = (low + high) / 2;
if (target == nums[middle])
{
return middle;
}
else if (target > nums[middle])
{
low = middle + 1;
}
else if (target < nums[middle])
{
high = middle - 1;
}
}
return -1;
}
2.3 请搜索有哪些排序算法,并用自己的理解对集中排序算法分别进行描述
- 排序算法:冒泡,选择,插入,希尔,快速,归并,堆,基数,计数;
- 冒泡算法:它重复地走访过要排序的数列,依次比较两个元素,如果他们的顺序错误就把他们交换过来。重复地进行直到没有再需要交换,说明该数列已经排序完成。(依次比较相邻的两个数,正序则不动,倒序则交换位置,如此循环,直到整个数组为有序为止)
- 选择排序:选择排序是每一次从待排序的数据元素中选出最小的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。
1·从待排序序列中,找到关键字最小的元素
2·如果最小元素不是待排序序列的第一个元素,将其和第一个元素互换
3·从余下的 N - 1 个元素中,找出关键字最小的元素,重复进行直到排序结束- 快速排序:从数列中挑出一个元素作为基准。重新排列数列,把所有的比基准小的放在基准前面,反之放在后面(一样大可任意一边)完成后基准处在分区的中间位置。
- 插入排序:插入排序是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在从后向前扫描的过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间通过递归调用把小于基准元素和大于基准元素的子序列进行排序。
- 归并排序:申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;设定两个指针,最初位置分别为两个已经排序序列的起始位置;比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;重复步骤3直到某一指针达到序列尾;将另一序列剩下的所有元素直接复制到合并序列尾。
- 希尔排列:选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;按增量序列个数k,对序列进行k 趟排序;每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
- 堆排列:指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
- 基数排列:是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。
- 桶排列:是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排)。
- 计数排列:不是基于比较的排序算法,其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
2.4 请给出本周学习总结
1·学习进度条
周/日期 | 这周所花的时间 | 代码行 | 学到的知识点简介 | 目前比较迷惑的问题 |
---|---|---|---|---|
第一周/3.3-3.8 | 7小时 | 245行 | 第6章 回顾数据类型和表达式,第12章 文件 | 对于刚接触的文件还不够熟悉,当数据过大时,减少运算时间这个问题还不是很了解 |
第二周/3.10-3.15 | 16小时 | 312行 | 一维数组,选择排序法和二分查找法 | 排列算法定义不明确,二分查找法不是很理解 |
2·累积代码行和博客字数
3·学习内容
- 学习的知识越来越深奥,有待继续提高
- 应该多花时间学习算法
- 对排序的理解
- 冒泡排序:将一组无序数列按从小到大的顺序排列,将相邻的两个数字进行比较,如果前者比后者大,则两数位置进行交换;反之,位置不变。
以5 6 3 1 8为例:
1.5与6进行比较,5比6小,位置不变;
2.6与3进行比较,6比3大,位置互换,数列变成5 3 6 1 8;
3.6与1进行比较,6比1大,位置互换,数列变成5 3 1 6 8;
4.6与8进行比较,6比8小,位置不变;
5.以这种方法进行下一轮排序。
#include <stdio.h>
#define ARR_LEN 255 /*数组长度上限*/
#define elemType int /*元素类型*/
/* 冒泡排序 */
/* 1. 从当前元素起,向后依次比较每一对相邻元素,若逆序则交换 */
/* 2. 对所有元素均重复以上步骤,直至最后一个元素 */
/* elemType arr[]: 排序目标数组; int len: 元素个数 */
void bubbleSort (elemType arr[], int len) {
elemType temp;
int i, j;
for (i=0; i<len-1; i++) /* 外循环为排序趟数,len个数进行len-1趟 */
for (j=0; j<len-1-i; j++) { /* 内循环为每趟比较的次数,第i趟比较len-i次 */
if (arr[j] > arr[j+1]) { /* 相邻元素比较,若逆序则交换(升序为左大于右,降序反之) */
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
int main (void) {
elemType arr[ARR_LEN] = {3,5,1,-7,4,9,-6,8,10,4};
int len = 10;
int i;
bubbleSort (arr, len);
for (i=0; i<len; i++)
printf ("%d ", arr[i]);
putchar ('
');
return 0;
}
- 插入排序:将一组数列分为有序和无序两个板块,然后将无序板块里的数字与有序板块里的数字进行对比,以插入的方式进入有序板块所对应的位置。
以5 6 3 1 8为例:
1.将5定为有序板块,6 3 1 8为无序板块;
2.将无序板块中的6抽出,与5进行比较,5比6小,将其插入5的后一位,将6列于有序板块,为5 6;
3.将无序板块中的3抽出,与5,6进行比较,3比5,6小,将其插入5的前一位,将3列为有序数列,为3 5 6;
4.将无序板块中的1抽出,与3,5,6进行比较,1比3,5,6小,将其插入3的前一位,将1列为有序数列,为1 3 5 6;
5.将无序板块中的8抽出,与1,3,5,6进行比较,8比1,3,5,6小,将其插入6的后一位,将8列为有序数列,为1 3 5 6 8;
6.排序完成,为1 3 5 6 8。
#include <stdio.h>
//自定义的输出函数
void print(int a[], int n ,int i){
printf("%d:",i);
for(int j=0; j<8; j++){
printf("%d",a[j]);
}
printf("
");
}
//直接插入排序函数
void InsertSort(int a[], int n)
{
for(int i= 1; i<n; i++){
if(a[i] < a[i-1]){//若第 i 个元素大于 i-1 元素则直接插入;反之,需要找到适当的插入位置后在插入。
int j= i-1;
int x = a[i];
while(j>-1 && x < a[j]){ //采用顺序查找方式找到插入的位置,在查找的同时,将数组中的元素进行后移操作,给插入元素腾出空间
a[j+1] = a[j];
j--;
}
a[j+1] = x; //插入到正确位置
}
print(a,n,i);//打印每次排序后的结果
}
}
int main(){
int a[8] = {3,1,7,5,2,4,9,6};
InsertSort(a,8);
return 0;
}
- 基数排序:准备0-9十个桶子,将数列中的个位数与桶号相同的数放入对应的桶子中,再将所以桶子里的数字按照0-9桶号的顺序重新排列,以相同的方法排列十位,百位等;
以73 22 93 43 55 14 28 65 39 81为例
1.将数列中的个位数与桶号相同的数放入对应的桶子中,0(无)1(81)2(22)3(43 73 93) 4(14) 5(55 65) 6(无) 7(无)8(28)9(39)
重新排列得81 22 73 93 43 14 55 65 28 39;
2.将数列中的十位数与桶号相同的数放入对应的桶子中,0(无)1(14)2(22)3(39) 4(43) 5(55) 6(65) 7(73)8(81)9(93)
重新排列得14 22 39 43 55 65 73 81 93;
3.排列完成。
#include<math.h>
testBS()
{
inta[] = {2, 343, 342, 1, 123, 43, 4343, 433, 687, 654, 3};
int *a_p = a;
//计算数组长度
intsize = sizeof(a) / sizeof(int);
//基数排序
bucketSort3(a_p, size);
//打印排序后结果
inti;
for(i = 0; i < size; i++)
{
printf("%d
", a[i]);
}
intt;
scanf("%d", t);
}
//基数排序
voidbucketSort3(int *p, intn)
{
//获取数组中的最大数
intmaxNum = findMaxNum(p, n);
//获取最大数的位数,次数也是再分配的次数。
intloopTimes = getLoopTimes(maxNum);
inti;
//对每一位进行桶分配
for(i = 1; i <= loopTimes; i++)
{
sort2(p, n, i);
}
}
//获取数字的位数
intgetLoopTimes(intnum)
{
intcount = 1;
inttemp = num / 10;
while(temp != 0)
{
count++;
temp = temp / 10;
}
returncount;
}
//查询数组中的最大数
intfindMaxNum(int *p, intn)
{
inti;
intmax = 0;
for(i = 0; i < n; i++)
{
if(*(p + i) > max)
{
max = *(p + i);
}
}
returnmax;
}
//将数字分配到各自的桶中,然后按照桶的顺序输出排序结果
voidsort2(int *p, intn, intloop)
{
//建立一组桶此处的20是预设的根据实际数情况修改
intbuckets[10][20] = {};
//求桶的index的除数
//如798个位桶index=(798/1)%10=8
//十位桶index=(798/10)%10=9
//百位桶index=(798/100)%10=7
//tempNum为上式中的1、10、100
inttempNum = (int)pow(10, loop - 1);
inti, j;
for(i = 0; i < n; i++)
{
introw_index = (*(p + i) / tempNum) % 10;
for(j = 0; j < 20; j++)
{
if(buckets[row_index][j] == NULL)
{
buckets[row_index][j] = *(p + i);
break;
}
}
}
//将桶中的数,倒回到原有数组中
intk = 0;
for(i = 0; i < 10; i++)
{
for(j = 0; j < 20; j++)
{
if(buckets[i][j] != NULL)
{
*(p + k) = buckets[i][j];
buckets[i][j] = NULL;
k++;
}
}
}
}
注:代码及动图为引用版块,其余为自我理解。