算法分析与设计
复习大纲
总纲
总复习
第一章 绪论
• 主要内容
• 算法的概念
• 算法问题求解基础
• 算法的描述
• 算法的设计技术
• 1、算法的重要特性
• (1)确定性
• 算法的每一种运算必须要有确切的定义,即每
一种运算应该执行何种动作必须相当清楚、无二义性。
• (2)能行性
• 算法中有待实现的运算都是相当基本的,每种
运算在原理上能在有限时间内完成。
• (3)输入
• 这些算法有0个或多个输入,这些输入是在算
法开始之前给出的量,它取自特定的对象集合。
• (4)输出
• 没有输出的算法是无效的算法,因此算法要有
一个或多个输出,这些输出是同输入有某种特定关系的
量。
• (5)有穷性
• 一个算法总是在执行了有穷步运算后终止。
• 阐明几个要点:
• 算法的每一步骤都必须清晰、明确。
• 算法所处理的输入的值域必须仔细定义。
• 同样一种算法可以用几种不同的形式来描述。• 可能存在集中解决相同问题的算法。
• 针对同一问题的算法可能会基于完全不同的解
题思路,而且解题速度也会有显著不同。
• 2、 算法问题求解基础
算法作为我们的研究对象,要讨论算法的各个方面,需要从以下几个方面入手。
• 1.理解问题
• 2.了解计算机设备的性能
• 3.在精确解法和近似解法间做选择
• 4.确定适当的数据结构
• 5.算法设计技术
• 6.详细表述算法的方法
• 7.证明算法的正确性
• 8.分析算法
• 9.为算法写代码
•
设计了一个算法,除了检查其正确性外,更重要的
是看该算法的效率了,算法有两种效率:时间效率及空间效率。
时间效率指的是该算法的执行需要多长时间;
空间效率指的是该算法的执行需要多少额外的存储空间。
此外,分析算法还要看它是否简单,或具有一般性。
• 所谓简单性:算法容易让人理解与实现,那它的效率
是否也很高吗?大多数情况下是否定的,但依然有些
简单的算法效率要高于复杂的算法,因此,我们在大
多数情况要在简单性与高效性之间进行权衡筛选。
• 所谓一般性:包含两层含义,一是解决问题所采用一
般性的算法,二是算法所接受输入的一般性。对于第
一方面,给出解决问题一般性的算法,可能更好解决。
如判断两个数是否互质的问题,即它们两个是否只拥
有惟一的公约数1。我们可以这样设计算法:先求这
两个数的公约数,再判断此数是否为1。我们也可以
设计其它的算法,但做起来可能是困难的或上完全不
可能的。对于第二个方面,输入的范围要一般,即在
测试算法时,不能仅凭借手头上仅有的几个数据进行
测试,要关心边界数据以及海量数据,这可能是用户
经常使用的输入数据。
一、蛮力法
• 人们是这样描述它的:蛮力法是一种简单
直接地解决问题的方法,常常直接基于问
题的描述和所涉及的概念定义。这里的
“ 力” 是指计算机的能“ 力” ,而不是人的智“ 力” 。我们也可以用“ 直接做吧!”
来描述蛮力法的策略。
• 冒泡排序:
冒泡排序
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
int arr[] = {34,24,67,435,97,46,34,742,34,23,89,657};
int n = sizeof(arr)/sizeof(arr[0]);
for (int i = 0; i < n-1; i++) {
// 外层循环控制比较次数
for (int j = 0; j < n-i-1; j++) {
// 遍历数组从0到n-i-1
// 交换如果找到元素大于下一个元素
if (arr[j] > arr[j+1]) {
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
for (int i = 0; i < n; i++) {
cout << arr[i] << " ";
}
return 0;
}
• 选择排序;
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
int arr[] = {34,24,67,435,97,742,34,23,89,657};
int n = sizeof(arr)/sizeof(arr[0]);
// 外层循环控制比较次数
for (int i = 0; i < n - 1; i++) {
int min_idx = i; // 假设当前位置为最小值索引
// 内层循环找到真正的最小值索引
for (int j = i + 1; j < n; j++) {
// 如果找到更小的元素,更新最小值索引
if (arr[j] < arr[min_idx]) {
min_idx = j;/// 更新最小值索引
}
}
// 手动交换找到的最小元素与i位置的元素
// 使用临时变量
if (min_idx != i) {
// 交换
int temp = arr[i];// 保存i位置的元素
arr[i] = arr[min_idx];// 将i位置的元素赋给min_idx
arr[min_idx] = temp;// 将i位置的元素赋给min_idx
}
}
for (int i = 0; i < n; i++) {
cout << arr[i] << " ";
}
return 0;
}
• 顺序查找
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
int arr[] = {34,24,67,435,97,46,34,742,34,23,89,657};
int n = sizeof(arr)/sizeof(arr[0]);
int key = 67;//要查找的元素
for (int i = 0; i < n; i++) {// 外层循环控制比较次数
// 遍历数组
if (arr[i] == key) {
// 如果找到元素,输出位置并返回
cout << "找到元素" << key << "的位置为" << i << endl;
return 0;
}
}
cout << "未找到元素" << key << endl;
return 0;
}
• 简单的字符串匹配算法。
#include <iostream>
#include <string.h>
using namespace std;
int string_match(char* text, const char* pattern) {
int textLen = strlen(text);
int patternLen = strlen(pattern);
// 遍历文本串的每一个可能的起始位置
for (int i = 0; i <= textLen - patternLen; i++) {
int j;
// 比较从当前起始位置开始的子串与模式串
for (j = 0; j < patternLen; j++) {
// 如果发现不匹配的字符,跳出循环
if (text[i + j] != pattern[j])
break;
}
// 如果j等于模式串的长度,说明找到了匹配
if (j == patternLen)
return i; // 返回匹配起始位置
}
// 未找到匹配,返回-1或其他表示未找到的值
return -1;
}
int main() {
char text[] = "Hello, this is a simple example.";
char pattern[] = "simple";
int index = string_match(text, pattern);// 调用匹配函数
if (index != -1) {
cout << "字符串下标:" << index << endl;// 输出匹配位置
} else {
cout << "文本未找到字符串." << endl;// 输出未找到匹配
}
return 0;
}
二、分治法
• 无论人们在祈祷什么,他们总是在祈祷一
个奇迹。每一个祈祷都可以简化为:伟大
的上帝呀,请让两个二相加不等于四吧。
——伊万·屠格涅夫(1818- 1883),俄国作家和短篇小说家
分治法可能是最著名的通用算法设计技术了。虽然它的名气可能和它那好记的名字有
关,但它的确是当之无愧的:很多非常有效
的算法实际上是这个通用算法的特殊实现。其实,分治法是按照以下方案工作的:
- 将问题的实例划分为同一个问题的几个较小 的实例,最好拥有同样的规模。
- 对这些较小的实例求解(一般使用递归方法, 但在问题规模足够小的时候,有时也会使用
一些其他方法)。
- 如果必要的话,合并这些较小问题的解,以 得到原始问题的解。
- 合并排序是一种分治排序算法。它把一个输入数组一分 为二,并对它们递归排序,然后把这两个排好序的子数组合并为原数组的一个有序排列。在任何情况下,这个
算法的时间效率都是Θ(nlogn),而且它的键值比较次数非
常接近理论上的最小值。它的主要缺点是需要相当大的
额外存储空间。
- 快速排序是一种分治排序算法,它根据元素值和某些事 先确定的元素的比较结果,来对输入元素进行分区。快速排序十分有名,这不仅因为对于随机排列的数组,它
是一种较为出众的nlogn效率算法,而且因为它的最差效
率是平方级的。
- 折半查找是一种对有序数组进行查找O(logn)效率算法。 它是应用分治技术的一个非典型案例,因为在每次迭代
中,它只需要解决两个问题的一个。
- 大整数乘法是一种处理两个n位整数相乘的分治算法。
- 分治技术可以成功地应用于两个重要的计算几何问题: 最近对问题和凸包问题。
三、减治法
减治技术利用了一种关系:一个问题给 定实例的解和同样问题较小实例的解之间
的关系。一旦建立了这样一种关系,我们
既可以从顶至下(递归地),也可以从底
至上(非递归地)地来运用。减治法有3种
主要的变种:
• 减去一个常量;
• 减去一个常数因子;
• 减去的规模是可变的。
在减常量变种中,每次算法迭代总是从实例规模 中减去一个规模相同的常量。一般来说,这个常量
等于一,但减二的情况偶尔也会发生,例如,有的算法会根据实例规模为奇数和偶数的不同情况,分别做不同的处理。这一类思维主要在排序问题中应用到。
减常因子技术意味着在算法的每次迭代中,总是 才实例的规模中减去一个相同的常数因子。在大多
数应用中,这样的常数因子等于二。
最后,在减治法的减可变规模变种中,算法在每 次迭代时,规模减小的模式都是不同的。计算最大
公约数的欧几里德算法是这种情况的一个很好的例
子。
这类问题是在组合问题中出现。
分治法和减治法区别
分治法是对分解的子问题分别求解,需要对子问题的解进行合并,而减治法只对一个子问题求解,并且不需要进行解的合并。应用减治法(例如减
半法)得到的算法通常具有如下递推式:
T(n) =
0
îíì T(n/ 2) +1
n =1 n >1
位串法生成幂集
• 例n=3,元素为{a1,a2,a3}
方法: 每一个子集与一个3位二进制串b1b2b3对应,ai属
于该子集时,bi=1,否则 bi=0
• 二进制串:
000, 001, 010, 011, 100, 101, 110, 111
• 对应子集:
, {a3}, {a2}, {a2,a3}, {a1}, {a1,a3}, {a1,a2}, {a1,a2,a3}
约瑟夫斯问题(Josephus)
• 在约瑟夫斯环中最后的幸存者是谁?
偶数情况:J(2k)=2J(k)-1
奇数情况:J(2k+1)=2J(k)+1
二进制表示:J(6)=J(1102)=1012=5,J(7)=J(1112)=1112=7
20
• 下列( )不是衡量算法的标准。
|
• A时间效率 B空间效率 C问题的难度 D
适应能力
• 下列程序段S执行次数为( )。
|
• for (i=1;i<=n;i++)
• for (j=0;j<=n;j++)
• S;
• A n2 B n2/2 C n(n+1) D n(n+1)/2
• 二维最近邻点问题,如果使用分治法,对于一个子集上的某一点,另一个子集上需
|
要检查的点的个数是( )。
• A 1个 B 2个 C 6个 D 8个
C
例题一
• 给出{23 13 49 6 31 19 28}采用快速排序思想
进行排序时划分的过程示意图。
左右为原数组的元素位置。
例题二
• 有n枚硬币,其中有一枚硬币是假币,且假
币的重量较轻,通过一架天平找出假币。
比较次数最少。
- 开始
- 输入:硬币总数 n
- 判断 n 是否为1
- 是:输出 硬币编号0为假币(或直接指出唯一一枚硬币为假币),结束
- 初始化:将硬币分为两组,尽量均等(若不能均分,一组会比另一组多一枚)
- 天平称重:将两组硬币分别放在天平的两端
- 根据天平状态分支:
- 如果天平平衡:假币在未参与本次称重的那一枚中,直接进入第9步
- 如果天平不平衡:假币在较轻的那一组中
- 更新范围:根据天平结果,缩小假币可能存在的范围至较轻的那一组
- 递归执行:从第4步开始,对新范围内的硬币重复上述过程
- 确定并输出假币位置:经过递归缩小范围后,最终确定并输出假币的确切位置
- 结束
例题三
• 【问题描述】
• 蛇形矩阵是由1开始的自然数依次排列成
的一个矩阵上三角形。
• 【要求】
• 【数据输入】本题有多组数据,每组数据
由一个正整数N组成。(N不大于100)
• 【数据输出】对于每一组数据,输出一个
N行的蛇形矩阵。两组输出之间不要额外的空
行。矩阵三角中同一行的数字用一个空格分
开。行尾不要多余的空格。
【样例输入】
• 5
• 【样例输出】
• 1 3 6 10 15
• 2 5 9 14
• 4 8 13
• 7 12
• 11
#include<iostream>
using namespace std;
int main()
{
int N,i,j,x,y;
cin>>N;//输入行数
x=1;
for(i=1;i<=N;i++) {//外循环:循环N次,共N行
x=x+i-1;
cout<<x<<" ";//输出每行首个数字
y=x;
for(j=i;j<N;j++) {//内循环:循环输出每行余下数字
y=j+y+1;
cout<<y<<" ";
}
cout<<endl;
}
return 0;
}
例题 四
• 在没有电话的时代,摩尔斯电码是无线电传输领
域中的一种常用代码。电码以短信号(短点,o)和
长信号(长点,-)的不同组合表示各种文字。例如
:o—表示英文字母J,而—表示英文字母M。
• 假设有一本以n个长点和m(n、m<=100)个短点
组成的、包含所有信号的字典。例如:n=m=2,就会
包含如下信号。
–oo
-o-o
-oo-
o–o
o-o-
oo–
• 这些信号已按照字典顺序排列好了。-的
ASKII码是45,而o的ASCII码是111。因此,
按照字典顺序,-在前,o在后。给定n和m
时,编写代码计算出此字典的第k(
k<=1,000,000,000,000)个信号。例如:上
述字典的第四个信号是o–o。