算法的基本性质
算法的定义:算法是解某一特定问题的一组有穷规则的集合
算法的特性
- 有限性
- 确定性(指的是每个动作都是确定的,并非执行结果确定)
- 输入
- 输出
- 能行性
用算法的复杂性衡量一个算法的效率
程序可以不满足算法的有限性
算法研究的核心问题是时间(速度)问题
算法的描述方法:自然语言、流程图、程序设计语言、伪码
算法设计的过程:问题分析、建立数学模型、算法设计与选择(结合数据结构,解决问题的核心)、算法分析、算法实现、程序调试、编制整理文档
初等操作
运行时间的上界:
O
O
O
运行时间的下界
运行时间的准确界
分治法
分治法的求解过程:划分子问题、求解子问题、合并子问题
适用条件
- 问题可以分解为若干规模较小、相同性质子问题(最优子结构)
- 子问题易于求解
- 子问题的解可以合并为原问题的解
- 各个子问题相互独立,即子问题之间不包含公共的子问题
独立子问题:各子问题之间相互独立
平衡子问题:使子问题的规模大致相同
贪心
最优子结构、自顶向下、局部最优解
基本要素:贪心选择性质和最优子结构性质
dp
基本要素:子问题重叠性质和最优子结构性质
dp 的基本思想
- 重叠子问题
- 多阶段决策
dp 的前提条件
- 最优化原理/最佳原则/最优子结构
- 无后向性/无后效性
记忆化搜索 = 递归 + 备忘录
dp 基本步骤
- 找出最优解的性质,并刻画其结构特征
- 递归地定义最优值
- 以自底向上地方式计算出最优值
- 根据计算最优值时得到的信息,构造最优解
简化步骤
- 分析最优解的性质,并刻画其结构特征
-
- 寻找更小的问题
- 递归地定义最优值
- 以自底向上的方式或自顶向下地记忆化方法(备忘录法)计算出最优值
- 根据计算最优值时得到的信息,构造问题的最优解
与分治法的相同点:都具有最优子结构性质
与分治法的区别:dp 适用于分解得到的子问题往往不是相互独立的(重叠子问题)
与贪心的相同点:都具有最优子结构性质
与贪心的区别:dp 往往依赖相关子问题的解,而贪心选择局部最优,再去解决相关子问题;dp 往往自底向上,而贪心往往自顶向下
数塔问题求解的特点
- 多阶段决策问题:将全过程求解分为若干阶段求解
- 递推性:在全过程最优路径中,将会出现阶段的最优路径
- 无后效性:前面确定的路径不会受后面计算的影响,后面的结果依据前面的最优结果来确定
- 动态规划:逐段求解最优路径,势必会找到一个全过程最优路径
快速排序
- 确定分界点
x
x
x
1.1 q [ l ] q[l] q[l]
1.2 q [ r ] q[r] q[r]
1.3 q [ l + r + 1 > > 1 ] q[l+r+1>>1] q[l+r+1>>1]
1.4 随机 - 调整区间:将所有小于等于 x x x 的数放在左边,大于等于 x x x 的数放在右边
- 递归处理左右两段
代码
void quick_sort(int q[],int l,int r)
{
if(l>=r)return ;
int i=l-1,j=r+1;
//int x=q[l+r+1>>1];
int x=q[l+rand()%(r-l+1)];
while(i<j)
{
do i++;while(q[i]<x);
do j--;while(q[j]>x);
if(i<j)swap(q[i],q[j]);
}
quick_sort(q,l,j);
quick_sort(q,j+1,r);
}
稳定性:不稳定
基数排序
是一种非比较性整数排序算法
算法思想:将整数按位切割为不同数字,按数字排序
排序方式:MSD(从高位开始排序,可采用计数排序)和 LSD(从低位开始排序,可采用桶排序)
复杂性分析
设最大位数为
d
d
d,基数为
r
r
r(十进制为
10
10
10),则时间复杂度为
O
(
d
×
(
n
+
r
)
)
O(d\times (n+r))
O(d×(n+r)),空间复杂度为
O
(
r
+
n
)
O(r+n)
O(r+n)
代码
int a[100005],tmp[100005],cnt[10],n;
int max_bit(int a[],int n)
{
int d=1,p=10;
for(int i=1;i<=n;i++)
while(a[i]>=p)
{
d++;
p*=10;
}
return d;
}
void radix_sort(int a[],int n)
{
int d=max_bit(a,n);
int radix=1;
for(int i=1;i<=d;i++)
{
memset(cnt,0,sizeof cnt);
for(int j=1;j<=n;j++)cnt[(a[j]/radix)%10]++;
for(int j=1;j<10;j++)cnt[j]=cnt[j]+cnt[j-1];
for(int j=n;j;j--)
{
int k=(a[j]/radix)%10;
tmp[cnt[k]]=a[j];
cnt[k]--;
}
memcpy(a,tmp,sizeof tmp);
radix*=10;
}
}
稳定性:稳定
时间复杂度递推方程的求解
Hanoi
算法:
Hanoi(A,C,n)将 A 柱上的 n 个盘子移动到 C 柱上
{
if(n==1)
move(A,C);//将 A 柱上的 1 个盘子移到 C 柱上
else
{
Hanoi(A,B,n-1);
move(A,C);
Hanoi(B,C,n-1);
}
}
设移动
n
n
n 个盘子的移动次数为
T
(
n
)
T(n)
T(n),则可得到 Hanoi 时间复杂度的递推方程:
T
(
n
)
=
2
×
T
(
n
−
1
)
+
1
T(n)=2\times T(n-1) +1
T(n)=2×T(n−1)+1
迭代计算,即
T
(
n
)
=
2
×
(
2
×
T
(
n
−
2
)
+
1
)
+
1
=
⋯
=
2
n
−
1
×
T
(
1
)
+
1
+
2
+
⋯
+
2
n
−
2
=
2
n
−
1
T(n)=2\times (2\times T(n-2)+1)+1=\dots =2^{n-1} \times T(1)+ 1+2+\dots +2^{n-2}=2^n-1
T(n)=2×(2×T(n−2)+1)+1=⋯=2n−1×T(1)+1+2+⋯+2n−2=2n−1
最后用数学归纳法代入验证:
T
(
n
)
=
2
×
(
2
n
−
1
−
1
)
+
1
=
2
n
−
1
T(n)=2\times (2^{n-1}-1)+1=2^n-1
T(n)=2×(2n−1−1)+1=2n−1
归并排序
设
T
(
n
)
T(n)
T(n) 为长度为
n
n
n 的数组排序需要比较的次数,得时间复杂度的递推表达式
T
(
n
)
=
2
×
T
(
n
/
2
)
+
n
−
1
T(n)=2\times T(n/2) +n-1
T(n)=2×T(n/2)+n−1,令
n
=
2
k
n=2^k
n=2k,代入,得
T
(
2
k
)
=
T
(
2
k
−
1
)
+
2
k
−
1
T(2^k)=T(2^{k-1})+2^k-1
T(2k)=T(2k−1)+2k−1,迭代计算,得
T
(
2
k
)
=
(
k
−
1
)
×
2
k
−
1
T(2^k)=(k-1)\times 2^k-1
T(2k)=(k−1)×2k−1,将
k
=
l
o
g
n
k=logn
k=logn 代入,得
T
(
n
)
=
n
l
o
g
n
−
n
+
1
T(n)=nlogn-n+1
T(n)=nlogn−n+1
非递归程序编写
二叉树遍历方式
- 前序遍历
非递归写法
vector<int> res;
void preOrder(TreeNode *root)
{
if(root==NULL)return ;
TreeNode *p=root;
stack<TreeNode*> stk;
while(p!=NULL || stk.size())
{
while(p!=NULL)
{
res.emplace_back(p->val);
stk.emplace(p);
p=p->left;
}
if(stk.size())
{
p=stk.top();
stk.pop();
p=p->right;
}
}
}
- 中序遍历
非递归写法
vector<int> res;
void midOrder(TreeNode* root)
{
if(root==NULL)return ;
stack<TreeNode*> stk;
TreeNode* p=root;
while(p!=NULL || stk.size())
{
while(p!=NULL)
{
stk.emplace(p);
p=p->left;
}
if(stk.size())
{
p=stk.top();
res.emplace_back(p->val);
stk.pop();
p=p->right;
}
}
}
- 后序遍历
非递归写法
vector<int> res;
void postOrder(TreeNode* root)
{
if(root==NULL)return ;
stack<TreeNode*>stk;
TreeNode* p=root,*pre;
while(p!=NULL || stk.size())
{
while(p!=NULL)
{
stk.emplace(p);
p=p->left;
}
p=stk.top();
stk.pop();
if(p->right==NULL || p->right==pre)
{
res.emplace_back(p->val);
pre=p;
p=NULL;
}
else
{
stk.emplace(p);
p=p->right;
}
}
}