专题研讨课二: 数组在解决复杂问题中的作用

Slides:



Advertisements
Similar presentations
九族文化村兩天一夜遊 組員 : 傅淳鈺 9A0E0019 黃湘蓉 4A 陳誌龍 9A0K0026 潘韋舜 9A0B0951 何奇龍 4A
Advertisements

While 迴圈 - 不知重複執行次數
CSIM, PU C Language Introduction to the C Programming Language 重覆敘述 (for,while,break,continue) 適合重複性的計算或判斷.
首页 全国高等学校招生考试统一考试 监考员培训 广州市招生考试委员会办公室.
人口增长.
第一章 会计法律制度 补充要点.
第五章 会计职业道德.
二、个性教育.
计算机三级考试C语言上机试题专题.
“八皇后”问题 崔萌萌 吕金华.
情緒與壓力管理─背部舒緩 指導老師:彭易璟 第六組組員:會資三乙 499A0047 謝宛霖 會資三乙 499A0019 吳汶諭
程序设计实习 3月份练习解答
项目五——校园一卡通程序功能模块化设计 5-1项目显示查询和退出函数设计.
让我们快快乐乐.
資料結構與演算法 課程教學投影片.
第十章 内部排序 10.1 概述 10.2 插入排序 直接插入排序 其他插入排序 希尔排序
C语言基础——指针的高级应用 Week 05.
第6章 轴测投影图 图6.1 三视图和轴测图 (a)三视图 (b)正等测 (c)斜二测.
Linked List Operations
第一章 程序设计入门.
复习与总结.
C语言程序设计 课程 第5章 数组 主讲:李祥 博士、副教授 单位:软件学院软件工程系.
循环结构又称为重复结构:用来处理需要重复处理的问题,它是程序中一种很重要的结构。
第3章 顺序结构程序设计 本章要点: 格式化输出函数──printf() 格式输入函数——scanf() 字符输出函数——putchar()
计算概论 第十八讲 C语言高级编程 结构与习题课 北京大学信息学院.
第2章回顾 标识符:不用记,动手 关键字:if, else, switch, for, while, do, break, continue, void, …… 局部变量和成员变量 ①变量作用域 ②内存布局 基本数据类型 ①4类8种 ②互相转换 流程控制语句 ①分支 if……else, switch.
计算机网络讲义 第5章 批量数据处理—数组 一维数组 排序和查找 二维数组 字符串.
Introduction to the C Programming Language
C语言 程序设计基础与试验 刘新国、2012年秋.
数 据 结 构 Ch.4 串 计 算 机 学 院 肖明军
中央广播电视大学开放教育试点课程 数据结构 辅导教师 倪政林.
2019/1/17 Java语言程序设计-程序流程 教师:段鹏飞.
第9章 内部排序 9.1 概述 9.2 插入排序 9.3 快速排序 9.4 选择排序 9.5 归并排序 9.6 基数排序
計數式重複敘述 for 迴圈 P
第5讲 结构化程序设计(Part II) 周水庚 2018年10月11日.
第 4 章 递 归 教学要求 本章重点 了解递归算法的概念与递归设计要领 掌握应用递归算法求解排序与选择、实现排列组合等典型案例
主讲人: 吕敏 { } Spring 2016 ,USTC 算法基础 主讲人: 吕敏 { } Spring 2016 ,USTC.
第1章 概述 本章要点: C语言程序结构和特点 C语言程序的基本符号与关键字 C语言程序的编辑及运行 学习方法建议:
第1讲 C语言基础 要求: (1) C程序的组成 (2) C语言的标识符是如何定义的。 (3) C语言有哪些基本数据类型?各种基本数
陣列 (Array)      授課老師:蕭志明.
计算机算法基础 周中成 苏州科技学院应用数学系 2019/4/18.
1.3 算法案例 第一课时.
C语言复习3----指针.
第三章 C++的语句和简单的程序设计 主要内容:
C++语言程序设计 C++语言程序设计 第三章 控制语句 第十一组 C++语言程序设计.
请编写程序在屏幕上打印出一个“*”? printf(”*\n”); 请编写程序在屏幕上打印四行,每行一个“*”?
第1章 绪论(二) 教学目标 理解算法的特性及评价标准 掌握算法时间复杂度和空间复杂度的分析方法 1/
Chap 5 函数 5.1 计算圆柱体积 5.2 使用函数编写程序 5.3 变量与函数.
7.1 C程序的结构 7.2 作用域和作用域规则 7.3 存储属性和生存期 7.4 变量的初始化
C语言程序设计 李祥 QQ:
C++语言程序设计教程 第2章 数据类型与表达式 第2章 数据类型与表达式 制作人:杨进才 沈显君.
第2章 认识C语言 教学要点 2. 1 项目二C语言程序识读 2 .2 项目三班级成绩排名 2 .3 知识链接 返回.
<编程达人入门课程> 本节内容 为什么要使用变量? 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ:
第二章 Java语法基础.
第2章 数据类型、运算符与表达式 本章要点: 基本数据类型 常量和变量 算术运算符和算术表达式 关系运算符和关系表达式
第二章 类型、对象、运算符和表达式.
累堆排序法 (Heap Sort).
陣列的位址計算.
第二章 基本数据类型 ——数据的表示.
本节内容 指针类型.
第六章 贪心算法.
第七章  数 组.
Chap 7 数 组 7.1 排序问题 7.2 找出矩阵中最大值所在的位置 7.3 进制转换.
第2章 Java语言基础.
基本資料型態 變數與常數 運算子 基本的資料處理 授課:ANT 日期:2014/03/03.
C/C++基礎程式設計班 陣列 講師:林業峻 CSIE, NTU 3/14, 2015.
第三章 流程控制 程序的运行流程 选择结构语句 循环结构语句 主讲:李祥 时间:2015年10月.
第六章 复合数据类型 指针的声明与使用 数组的声明与使用 指针与数组的相互引用 字符串及相关库函数 new与delete
本节内容 指针类型 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
C语言基础学习 从外行到入门.
Presentation transcript:

专题研讨课二: 数组在解决复杂问题中的作用 专题研讨课二: 数组在解决复杂问题中的作用

Part I 要点提示

一维数组 类型名 数组名[数组长度] int a[5],b[10]; float x[7]; double y[20]; 定义: 类型名 数组名[数组长度] 每个元素的类型 数组变量的名称,合法的标识符 数组的大小,整型常量表达式 为什么数组长度不能是变量? int N=10; int a[N]; int a[5],b[10]; float x[7]; double y[20];

一维数组 初始化:类型名 数组名[数组长度] = {初值表}; 初始化:类型名 数组名[数组长度] = {初值表}; int a[10] = { 0,1, 2, 3, 4, 5, 6, 7, 8, 9 }; int a[ ] = { 0, 1, 2, 3, 4, 5, 6, 7, 9, 9 }; 部分初始化: int a[10] = { 0, 1, 2, 3, 4 }; 没有初始化的元素值是什么?

一维数组 引用:数组名[下标]; 数组变量的名称 整型表达式 例如:int a[10], i; 那么:a[ i ] 引用数组a的第i个元素

一维数组 int a[10]; 存储方式: 连续存放于内存(RAM) 数组名代表其首元素的地址 元素a[ i ] 的地址记为:a + i 2 3 4 5 6 7 8 ⁞ 9 a 内存 int a[10]; 元素a[ i ] 的地址记为:a + i a[ i ] 的实际地址是什么?

对于 <0 或者 >N-1 的k, a[k]是哪个元素? 一维数组 存储方式: 连续存放于内存(RAM) 数组名代表其首元素的地址 1 2 3 4 5 6 7 8 ⁞ 9 a 内存 对于长度为N的数组,末尾元素为a[N-1] 对于 <0 或者 >N-1 的k, a[k]是哪个元素? 修改a[k]有何后果?

可执行程序 指令:运算指令、控制指令 数据:常量、变量 由编译器根据源程序编译链接生成 处理数据时,必须要知道其大小 简单变量类型 数据变量类型和长度 自定义结构先定义

数组越界 对于<0的k:改变a[k]可能导致数组前面的数据/代码异常 内存 ⁞ 对于<0的k:改变a[k]可能导致数组前面的数据/代码异常 对于>=N的k:改变a[k]可能导致数组后面的数据/代码异常 数组a 长度N ⁞ 试图修改系统相关的数据和代码 将会被阻止执行

二维数组 类型名 数组名[行长][列长] int a[2][4]; float x[5][10]; double y[1][1]; 定义: 类型名 数组名[行长][列长] 每个元素的类型 数组变量的名称,合法的标识符 数组的大小,整型常量表达式 int a[2][4]; float x[5][10]; double y[1][1];

二维数组 部分初始化: 初始化: int a[3][2] = { {1}, {3, 4} }; 初始化了两行 第一行只初始化部分元素 int b[3][2] = { , {1, ,3 }}; 那些被初始化了?

二维数组 存储方式:逐行连续存放于RAM中 int a[3][2]; 二维数组的元素在内存中按行方式存放 a[0][0] a[0][1] 3 行 2 列, 6 个元素 表示1个3行2列的矩阵 二维数组的元素在内存中按行方式存放 a[0][0] a[0][1] a[1][0] a[1][1] a[2][0] a[2][1] 第0行a[0] 第1行a[1] 第2行a[2] a[0][0] a[0][1] a[1][0] a[1][1] a[2][0] a[2][1]

数组应用 —— 存储数据 大量数据 … 相同类型 循环遍历 文字信息、数据统计、图像视频 所有元素具有相同的类型/结构 处理方式相同 一般利用循环语句进行处理

数组应用 数值计算 向量运算 矩阵运算 多元方程 算法设计 制作查找表 简化程序结构

数组使用 设计函数处理数组 数组作为函数参数 void ReverseIntArray( int array[], int n ) { int i, k, tmp; for (i = 0, k=n-1; i < k; i++, k--) { tmp = array[i]; array[i] = array[k]; array[k] = tmp; }

形参数组array(的地址值)等于实参数组a(的地址值),于是它们指向同一块内存。因此,对array的操作等价于对a的操作。 1000 1000 1002 1004 1006 1008 1010 1012 1014 1494 1496 1498 1 2 3 4 5 . a [0] a [1] a [2] a [3] a [4] a [5] a [6] a [7] a [247] a [248] a [249] array[0] array[1] array[2] array[3] array[4] array[5] array[6] array[7] array[247] array[248] array[249] array 1000 假设a数组的首地址为1000,当执行 ReverseIntArray(a, 5) 形参数组array(的地址值)等于实参数组a(的地址值),于是它们指向同一块内存。因此,对array的操作等价于对a的操作。

Part II 应用举例

1:数组排序 输入n个数,将它们从小到大排序后输出。 3 5 2 8 1 选择法排序:选择最小的元素,交换到前列 3 5 2 8 1 3 5 2 8 1 (1) 1 5 2 8 3 (2) 1 2 5 8 3 (3) 1 2 3 8 5 (4) 1 2 3 5 8

一般地, (1) a[0] ~ a[n-1] 中找最小数,与 a[0] 交换 (k) ……(第k轮) (n-k个数中最小的) (n-1) a[n-2],a[n-1]中最小数,与 a[n-2] 交换 (2个数中最小的)

排序主程序 SortArray void SortArray(int array[ ], int n) { int k, iMin; for (k = 0; k < n-1; k++) { /* 找到第k次最小值的下标 */ iMin = FindMinIndex( array, k, n ); if ( k != iMin ) /*交换*/ SwapArrayElements(array, iMin, k); }

找最小值 FindMinIndex int FindMin(int a[ ], int start, int n) { int k, m; m = start; /*初始化为起始元素的下标*/ for ( k = start+1; k < n; k++ ) if ( a[k] < a[m] ) m = k; /*找到更小的,更新下标*/ return m; }

交换 SwapArrayElements void SwapArrayElements(int a[ ], int i, int j) { int tmp; tmp = a [i]; a [i] = a [j]; a [j] = tmp; }

思考 如何将数组从大到小进行排序? 想想排序的其他方法?

2:数组查找 在一个整数数组a[ ]中,查找给定的整数x int Find( int a[ ], int n, int x) 返回整数x在数组a中的下标。 如不存在,则返回-1。 n是数组a中元素的个数

Naïve方法 逐个查询 int Find( int a[ ], int n, int x ) { int i; for( i=0; i<n; i++ ) if( a[i]==x ) return i; return -1; }

二分查找 假设数组a具有从小到大的顺序 思想:将数据一分为二,在包含x的1/2数组中继续查找。直至找到或查找范围为空 s t k s、t是数组的首尾下标(不包括t) k k为中分下标 s t a[k] < x t s a[k] > x a[k]==x return k

二分查找 int Find( int a[ ], int n, int x ) { int s = 0, t = n, k; if( n<1 || x<a[0] || x>a[n-1] ) return -1; while( s < t ) { k = (s + t) / 2; if( a[k]<x ) s = k+1; else if ( a[k]>x ) t = k; else return k; } s t k s t t s

二分查找 利用了数组结构的优点 连续存放 随机存取

3:数组循环移位 将一个长度为n的数组A[ ],向右循环m个位置: A[0] A[1] A[2] …….. … A[n-2] A[n-1] 变成 A[n-m] …A[n-1] A[0] A[1]…A[n-m-1] (1)不能使用额外的数组 (2)不妨假设0<m<n,否则取m=m%n

Naïve方法 重复执行m次,每次将数组向右移动1位 总的移位次数为m*n void shift( int A[], int n ) { int i, e = A[n-1]; for( i=n-1; i>0; i-- ) A[i] = A[i-1]; A[0] = e; } void main() { … for( i=0; i<m; i++ ) shift(A,n); } 总的移位次数为m*n

翻转方法 A[0] A[1] A[2] A[3] … A[n-2] A[n-1] 整体翻转 A[n-1]… A[n-m] A[n-m-1]…A[1] A[0] 分段翻转 A[n-m] …A[n-1] A[0] A[1] … A[n-m-1]

翻转方法 void reverse(int A[ ], int s, int t) { int temp; while (s<t ) { temp = A[s]; A[s++] = A[t]; A[t--] = temp; } void main() { … reverse(A, 0, n-1 ); reverse(A, 0, m-1); reverse(A, m, n-1 ); } 总的移位次数为2*n

移位次数比较: m*n VS 2*n 当m>2时,翻转法的效率更高 思考更好的方法?(移位次数为n) 处理大数组(大数据)时,审慎考虑算法效率

4:扑克牌计算24点 4张扑克牌,各代表1~13之间的1个整数 采用+,-,*,/ 四种算术运算,得到24。 (利用数组建立查找表,简化代码) 4张扑克牌,各代表1~13之间的1个整数 采用+,-,*,/ 四种算术运算,得到24。 如能,输出算式;如不能,输出NO。 可以添加括号,改变运算优先级

解决思路 a P b Q c R d 考虑4个整数算式的各种组合情况 操作数 a,b,c,d 可以任意排列 24种情况 运算符P,Q,R都可以4种运算之一 64种情况

解决思路 a P b Q c R d 考虑4个整数算式的各种组合情况 P,Q,R的运算顺序有6种情况 PQR,PRQ,QPR,QRP,RPQ, RQP PRQ和RPQ的结果是一样的 (a P b)Q(c R d) 所以只需要考虑5种: PQR,PRQ,QPR,QRP,RQP

解决思路 逐个情况,分别处理  建立查找表,循环处理  分析各种情况, 将参数保存在数组中 操作数、运算符

程序设计 运算符数组:char op[64][3]; char opc[] = “+-*/”; n=0; for( i=0; i<4; i++) for( j=0; j<4; j++) for(k=0; k<4; k++) { op[n][0]=opc[ i ]; op[n][1]=opc[ j ]; op[n][2]=opc[ k]; n++; }

程序设计 操作数数组:int abcd[24][4]; /* 4张牌的点数保存在 int a[4] 中*/ n=0; for( i=0; i<4; i++) for( j=0; j<4; j++) for( k=0; k<4; k++) for(m=0; m<4; m++) { if( i==j || i==k || i==m || j==k || j==m || k==m ) continue; abcd[n][0] = a[i]; abcd[n][1] = a[j]; abcd[n][2] = a[k]; abcd[n][3] = a[m]; n++; }

程序设计 运算测试 found = 0; for( n=0; n<24 && !found; n++ ) for( m=0; m<64 && !found; m++ ) { /*提取运算符和操作数*/ a = abcd[n][0]; b = abcd[n][1]; c = abcd[n][2]; d = abcd[n][3]; P = op[m][0]; Q = op[m][1]; R = op[m][2]; if( found = test(a,b,c,d,P,Q,R) ) break; }

测试函数 int test( int a, int b, int c, int d, char P, char Q, char R) { /* PQR: ((a P b) Q c) R d */ if( calc( calc( calc(a,P,b), Q, c), R, d)==24 ) { printf(…….); return 1; } /* PRQ: (a P b) Q (c R d) */ if( calc( calc(a, P, b), Q, calc(c, R, d) )==24 ) { printf(…….); return 1; } 【其他情况雷同处理,略…】 }

calc函数 double calc( double a, char op, double b) { if( op==‘+’ ) return a+b; if( op==‘-’ ) return a-b; if( op==‘*’ ) return a*b; if( op==‘/’ && b!=0 ) return a/b; else return 1.0E10; }

4:扑克牌计算24点 程序的运算量是多少? 如何找出所有、不同的算式? 扩展:N张扑克牌 N-1个运算符 得到结果M

The End!

5:扩展的扑克牌问题 难度转移 操作数排列情况急剧增加:N! 运算符情况急剧增加:4^(N-1) 存储空间急剧增长 测试函数的处理的优先级情况急剧增加:(N-1)! 无法采用固化的分支程序处理

N元算式的优先级分析与计算 a0 P1 a2 P2 … Pn an 每执行一个运算之后,算式减少一元 适合采用递归方法进行测试和计算

N元算式的递归计算 int test( double a[], char p[], int N, int M ) { int i; double s, a1[54], p1[54]; if( N==2 ) { if( (s = calc(a[0], p[0], a[1]) )==M ) printf(…); return s==M; } for( i=0; i<N-1; i++ ) { s = calc(a[i], p[i], a[i+1]); 构造对应的N-1元算式,并存储到数组a1[ ]和p1[ ]中 if ( test( a1, p1, N-1, M ) ) return 1; return 0;

5:扩展的扑克牌问题 参考上述的递归的test函数,编写程序解决扩展的扑克牌问题。 假设N<10 打印算式

6:螺旋方阵 如下图,已知N*N(3<=N<=25)的方阵,从左上角第1个格子开始,按顺时针螺旋方向将1、2、3……的数据布满整个方阵。

基本思路: 螺旋方阵数据的产生有一定规律。从外到内,可以圈为单位生成数据。比如N=4时,刚好就是两圈数据: 第1圈: 1 2 3 4 1 2 3 4 12 5 11 6 10 9 8 7 第2圈: 13 14 16 15 基本规律是:当N为偶数时,需要N/2圈;当N为奇数时,也需要N/2圈,但是最后还生成一个数据就是N*N。 实现要点:使用二维数组存储螺旋方格子中的数据;二维数组所有数据的生成,可应用循环,根据所需要的圈数,从左右下左上,四个方向来产生数据。

[main] #define MAX_SCREW 20 /*螺旋最大边长*/ int InputScrewArray(); void GenScrewArray(int ScrewArray[MAX_SCREW][MAX_SCREW], n); void PrintScrewArray(ScrewArray[MAX_SCREW][MAX_SCREW], n); main() { int ScrewArray[MAX_SCREW][MAX_SCREW];/*二维螺旋矩阵*/ int n;/*实际边长*/ n = InputScrewArray(); /*输入螺旋边长*/ GenScrewArray(ScrewArray, n); /*生成螺旋矩阵*/ PrintScrewArray(ScrewArray, n);/*打印螺旋矩阵*/ }

[GenScrewArray] void GenScrewArray(int ScrewArray[MAX_SCREW][MAX_SCREW], int n) { int row, col; /*每一圈的左上角起始坐标*/ int len; /*每一圈的边长*/ int count; /*螺旋计数,每填上一个数,自增1*/ int i, k; row = col = 0; /*起始位置(0,0)*/ len = n; /*最外圈边长*/ count = 1;/*第一个数是1*/ for (i = 0; i < n/2; i++) { /*圈数循环*/ for (k=col;k<col+len-1;k++) ScrewArray[row][k]=count++;/*上:左到右*/ for (k=row;k<row+len-1;k++) ScrewArray[k][col+len-1]=count++;/*右:上到下*/ for (k=col+len-1;k>col;k--) ScrewArray[row+len-1][k]=count++;/*下:右到左*/ for (k=row+len-1;k>row;k--) ScrewArray[k][col]=count++;/*下:右到左*/ row++; col++; len -= 2;/*下一圈的左上角起始位置与边长调整*/ } if (n%2) ScrewArray[n/2][n/2] = count; /*奇数边长,补填中心元素值n*n */ }