《算法设计与分析基础》【part1】

【算法设计与分析】回溯法解决运动员配对问题(课程设计) 针对运动员最佳配对问题,本文利用回溯法寻求竞赛优势得分最优解,研究男女运动员最佳配对法,使各组男女双方竞赛优势的总和达到最大。针对这一问题,本题采用的是男运动员选女运动员的方法,构成了一棵排列树。树的结点表示女运动员,排列树的层数表示男运动员,经过算法处理后,输出符合最优值的编号。算例结果显示:男1号和女1号组合、男2号和女3号组合,男3号和女2号组合,竞赛优势最大。该算法简便、易懂,又有比较好的实用性和技巧性。算例结果显示:男1号和女1号组合、男2号和女3号组合,男3号和女2号组合,竞赛优势最大。该算法简 阅读详情

Chapter 1 绪论

数据结构的基本概念:

数据结构三要素:逻辑结构、物理(存储)结构、数据的运算。

线性数据结构:数组(Array)和链表(Linked list);栈(Stack)和队列(Queue);堆(Heap)。

树形数据结构:无序树和有序树。

图形数据结构:有向图和无向图。

集合与字典:集合是互不相同项的无序组合(可以为空);字典的结构为{key : value}。

抽象数据类型(ADT):由一个表示数据项的抽象对象集合和一系列对这些对象所做的操作构成。

算法的基本概念:

  • 程序 = 数据结构(Data Structure) + 算法(Algorithm)
  • 五大特性:
    • 有穷性
    • 确定性
    • 可行性
    • 输入和输出
  • “好”算法的特质:
    • 正确性、可读性、健壮性、高效率与低存储量需求。

**算法的在位性(in-place):**算法排序时,不借助额外的存储空间,特别的存储单元除外。

欧几里得算法:

gcd(m,n)算法思想

①如果 n = 0 n=0 n=0,返回的 m m m值作为结果,同时过程结束;否则进入②;

② m m m除以 n n n,将余数赋给 r r r;

③将 n n n的值赋给 m m m,将 r r r的值赋给 n n n,返回①。

伪代码描述

Euclid(m,n)
//使用欧几里得算法计算gcd(m,n)
//输入:两个不全为0的非负整数m,n
//输出:m,n的最大公约数
while n ≠ 0 do
    r ← m mod n
    m ← n
    n ← r
return m

1.算法问题求解

  • 理解问题(如确定算法输入——所解问题的实例往往需要考虑”边界值“);
  • 确定精确解法与近似解法(一些重要问题很多情况下无法求得精确解,如求平方根等);
  • 算法的设计技术(也叫”策略“或者”范例“,如蛮力法、分治法等);
  • 确定适当的数据结构;
  • 算法的描述(算法思想文字描述、伪代码[pseudocode]或者流程图[flowchart]等);
  • 算法的正确性证明;
  • 算法分析(效率[efficiency]分析:时间效率和空间效率);
  • 代码编写。

exercise 1.求方程 a x 2 + b x + c = 0 ax^2+bx+c=0 ax2+bx+c=0的根

//求方程ax^2+bx+c=0的根;利用求根公式
#include <stdio.h>
#include <math.h>
int main()
{
	float a,b,c,dis,x1,x2;
	printf("请输入a,b,c的值: ");
	scanf("%f %f %f",&a,&b,&c);
	dis=b*b-4*a*c;
	if(dis<0)
	{
		printf("该函数无实根。\n");
		return 0;
	}
	else if(dis==0)
	{
		x1=x2=(-b)/(a*a);
		printf("该函数有两个相等的实根:\n");
	}
	else 
	{
		x1=(-b+sqrt(dis))/(2*a);
		x2=(-b-sqrt(dis))/(2*a);
		printf("该函数有两个不等的实根:");
	}
	printf("x1=%f x2=%f\n",x1,x2);
	return 0;}

exercise 2.整型转2进制

#include "stdio.h"
#include <string.h>
void dectobin(const long dec,char *pbin)
{
  long ys=0;  // 余数。
  int s=dec;  // 商。
  int ii=0;   // 位数的计数器。
  char result[65];  // 十进制转换成二进制后,保存在result中,再反过来存放到pbin中。
  memset(result,0,sizeof(result));
  // 把十进制转换为二进制,存放在result中。
  while (s>0)
  {
    ys=s%2;
    s=s/2;
    result[ii]=ys+'0';
    ii++;
  }
  // 再把result字符串反过来,存放在pbin中。
  int jj=0;
  for (;ii>0;ii--)
  {
    pbin[jj]=result[ii-1];
    jj++;
  }
  pbin[jj]=0; // 出于安全的考虑,加上0表示字符串结束。
}

int main()
{
  long ii=12;
  char str[65];
  dectobin(ii,str);
  printf("%d的二进制输出是:%s\n",ii,str);
}

2. 重要问题类型

  • 排序

​ 常常对数字、字符的列表;记录(如数据库对数据表中一条条数据)进行按照选定的信息(键,key)进行排序。

​ 通常,排序算法如果保证了等值元素在输入中的相对顺序,可以说它的稳定的(stable)。

  • 查找

​ 在给定的集合中找一个给定的值(查找键,search key)。

​ 如果应用里的数据相对于查找次数频繁变化,查找问题需要考虑在数据集合中添加或删除元素的情况。

  • 字符串处理(字符串匹配问题)
  • 图问题
  • 组合问题(combinatorial problems)
  • 几何问题(geometric problems)

最近对(closest-pair)问题:给定平面上n个点中,距离最近的两个点;

凸包(convex-hull)问题:找一个能把给定集合中所有点都包含在内的最小凸边形。

  • 数值问题

涉及具有连续性的数学问题:解方程、计算定积分以及求函数值等;这类问题的特点是只能近似求解。

Chapter 2 算法效率分析基础

分析算法的时间复杂度和空间复杂度。

算法的输入规模

如对 n n n次多项式求值问题,这个参数是多项式的次数,或者是它系数的个数。

运行时间的度量单位

​ 对于大规模的输入,效率分析框架忽略乘法常量,而仅关注执行次数的增长次数(order of growth)及其常数倍。 T ( n ) ≈ c o p C ( n ) T(n)≈c_{op}C(n) T(n)≈cop​C(n),如 C ( n ) = 1 2 n ( n − 1 ) C(n)=\frac{1}{2}n(n-1) C(n)=21​n(n−1)为执行基本操作的次数, T ( n ) = O ( n 2 ) T(n)=O(n^2) T(n)=O(n2)

增长次数
log ⁡ 2 n < n < n log ⁡ 2 n < n 2 < 2 n < n ! 因为 log ⁡ a n = log ⁡ a b log ⁡ b n , 可以忽略对数的底,简写为 log ⁡ n \log_{2}n < n < n\log_{2}n < n^2 < 2^n < n! \\ 因为\log_{a}n = \log_{a}b\log_{b}n,可以忽略对数的底,简写为\log{n} log2​n<n<nlog2​n<n2<2n<n!因为loga​n=loga​blogb​n,可以忽略对数的底,简写为logn

1.渐进符号

非正式地来说

O ( g ( n ) ) O(g(n)) O(g(n))是增长次数小于等于 g ( n ) g(n) g(n)(及其常数倍, n → + ∞ n\rightarrow +\infty n→+∞)的函数集合
n ∈ O ( n 2 ) , 100 n + 5 ∈ O ( n 2 ) , 1 2 n ( n − 1 ) ∈ O ( n 2 ) n \in O(n^2),100n+5 \in O(n^2),\frac{1}{2}n(n-1) \in O(n^2) n∈O(n2),100n+5∈O(n2),21​n(n−1)∈O(n2)

Ω ( g ( n ) ) \Omega(g(n)) Ω(g(n))代表增长次数大于等于 g ( n ) g(n) g(n)(及其常数倍, n → + ∞ n\rightarrow +\infty n→+∞)的函数集合
n 3 ∈ Ω ( n 2 ) , 1 2 n ( n − 1 ) ∈ Ω ( n 2 ) , 但是 100 n + 5 ∉ O ( n 2 ) n^3 \in \Omega(n^2),\frac{1}{2}n(n-1) \in \Omega(n^2),但是100n+5 \notin O(n^2) n3∈Ω(n2),21​n(n−1)∈Ω(n2),但是100n+5∈/O(n2)
Θ ( g ( n ) ) \Theta(g(n)) Θ(g(n))代表增长次数等于 g ( n ) g(n) g(n)(及其常数倍, n → + ∞ n\rightarrow +\infty n→+∞)的函数集合

2.非递归算法的数学分析

对数的性质:
l o g a x y = y log ⁡ a x a log ⁡ b x = x log ⁡ b a log ⁡ a x = log ⁡ b x log ⁡ b a = log ⁡ a b log ⁡ b x log_{a}x^{y} = y\log_{a}x \\ a^{\log_{b}x} = x^{\log_{b}a} \\ \log{a}x = \frac{\log_{b}x}{\log_{b}a} = \log_{a}b\log_{b}x loga​xy=yloga​xalogb​x=xlogb​alogax=logb​alogb​x​=loga​blogb​x
组合:
一个 n 元素集合的排列数量: P ( n ) = n ! 一个 n 元素集合中 k 个元素的组合数: C n k = n ! k ! ( n − k ) ! 一个 n 元素集合的子集数: 2 n 一个n元素集合的排列数量:P(n)=n! \\ 一个n元素集合中k个元素的组合数:C_{n}^{k} = \frac{n!}{k!(n-k)!} \\ 一个n元素集合的子集数:2^n 一个n元素集合的排列数量:P(n)=n!一个n元素集合中k个元素的组合数:Cnk​=k!(n−k)!n!​一个n元素集合的子集数:2n
重要求和公式:
∑ i = l u 1 = 1 + 1 + ⋯ + 1 ⏟ u − l + 1 ( u , l 为整数边界,且 l ≤ u ) ; ∑ i = 1 n 1 = n ∑ i = 1 n i = 1 + 2 + ⋯ + n = n ( n + 1 ) 2 ∑ i = 1 n i 2 = 1 2 + 2 2 + ⋯ + n 2 = n ( n + 1 ) ( 2 n + 1 ) 6 = 1 3 n 3 ∑ i = 1 n i k = 1 k + 2 k + ⋯ + n k ≈ 1 k + 1 n k + 1 ∑ i = 0 n a i = 1 + a + ⋯ + a n = a n + 1 − 1 a − 1 ( a ≠ 1 ) ; ∑ i = 0 n 2 i = 2 n + 1 − 1 ∑ i = 1 n i 2 i = 1 × 2 + 2 × 2 2 + ⋯ + n 2 n = ( n − 1 ) 2 n + 1 + 2 ∑ i = 1 n 1 i = 1 + 1 2 + ⋯ + 1 n ≈ ln ⁡ n + γ , 其中 γ ≈ 0.5772 … ( 欧拉常数 ) ∑ i = 1 n lg ⁡ i ≈ n lg ⁡ n 注意: ∑ i = 1 n a i = ∑ i = 1 m a i + ∑ i = m + 1 n a i ∑ i = l u ( a i − a i − 1 ) = a u − a l − 1 近似计算: ∫ l − 1 u f ( x ) d x ≤ ∑ i = l u f ( i ) ≤ ∫ l u + 1 f ( x ) d x , f ( x ) 非递减 ∫ l u + 1 f ( x ) d x ≤ ∑ i = l u f ( i ) ≤ ∫ l − 1 u f ( x ) d x , f ( x ) 非递增 \sum_{i=l}^{u}1 = \underbrace{1+1+ \dots +1}_{u-l+1} \quad (u,l为整数边界,且l\leq u); \sum_{i=1}^{n}1 = n \\ \sum_{i=1}^{n}i = 1+2+\dots+n = \frac{n(n+1)}{2} \\ \sum_{i=1}^{n}i^2 = 1^2 + 2^2 + \dots + n^2 = \frac{n(n+1)(2n+1)}{6} = \frac{1}{3}n^3 \\ \sum_{i=1}^{n}i^k = 1^k + 2^k + \dots + n^k ≈ \frac{1}{k+1}n^{k+1} \\ \sum_{i=0}^{n}a^i = 1 + a + \dots + a^n = \frac{a^{n+1}-1}{a-1}(a \neq 1);\sum_{i=0}^{n}2^i = 2^{n+1}-1 \\ \sum_{i=1}^{n}i 2^i = 1\times{2} + 2\times{2^2} + \dots + n{2^n} = (n-1)2^{n+1}+2 \\ \sum_{i=1}^{n}\frac{1}{i} = 1 + \frac{1}{2} + \dots + \frac{1}{n} ≈ \ln{n}+\gamma,其中\gamma≈0.5772\dots(欧拉常数) \\ \sum_{i=1}^{n}\lg{i} ≈ n\lg{n} \\ 注意:\sum_{i=1}^{n}a_{i} = \sum_{i=1}^{m}a_{i} + \sum_{i=m+1}^{n}a_{i} \qquad \sum_{i=l}^{u}(a_{i} - a_{i-1}) = a_{u} - a_{l-1} \\ 近似计算:\int_{l-1}^u f(x) dx \leq \sum_{i=l}^{u}f(i) \leq \int_{l}^{u+1} f(x) dx,f(x)非递减\\ \int_{l}^{u+1} f(x) dx \leq \sum_{i=l}^{u}f(i) \leq \int_{l-1}^{u} f(x) dx,f(x)非递增 i=l∑u​1=u−l+1 1+1+⋯+1​​(u,l为整数边界,且l≤u);i=1∑n​1=ni=1∑n​i=1+2+⋯+n=2n(n+1)​i=1∑n​i2=12+22+⋯+n2=6n(n+1)(2n+1)​=31​n3i=1∑n​ik=1k+2k+⋯+nk≈k+11​nk+1i=0∑n​ai=1+a+⋯+an=a−1an+1−1​(a=1);i=0∑n​2i=2n+1−1i=1∑n​i2i=1×2+2×22+⋯+n2n=(n−1)2n+1+2i=1∑n​i1​=1+21​+⋯+n1​≈lnn+γ,其中γ≈0.5772…(欧拉常数)i=1∑n​lgi≈nlgn注意:i=1∑n​ai​=i=1∑m​ai​+i=m+1∑n​ai​i=l∑u​(ai​−ai−1​)=au​−al−1​近似计算:∫l−1u​f(x)dx≤i=l∑u​f(i)≤∫lu+1​f(x)dx,f(x)非递减∫lu+1​f(x)dx≤i=l∑u​f(i)≤∫l−1u​f(x)dx,f(x)非递增

通用方案:

  • 输入规模确定;
  • 找出算法的基本操作(一般位于算法最内层循环中);
  • 检查基本操作的执行次数是否只依赖于输入规模;
  • 算法基本操作执行次数的求和表达式。

3.递归算法的数学分析

递归算法一般都有程序出口(递归停止条件)
递推方程: x ( n ) = x ( n − 1 ) + n , 其中 n > 0 初始条件: x ( 0 ) = 0 递推方程:x(n) = x(n-1) + n ,其中n > 0 \qquad 初始条件:x(0) = 0 递推方程:x(n)=x(n−1)+n,其中n>0初始条件:x(0)=0
前向替换法(forward substutution):
递推式: x ( n ) = 2 x ( n − 1 ) + 1 ,其中 n > 1 x ( 1 ) = 1 前面几项: x ( 1 ) = 1 ; x ( 2 ) = 2 x ( 1 ) + 1 = 3 ; x ( 3 ) = 2 x ( 2 ) + 1 = 7 ; … 数学归纳法总结规律 ( 通项 ) : x ( n ) = 2 n − 1 , n = 1 , 2 , 3 … 递推式:x(n) = 2x(n-1)+1,其中n>1 \qquad x(1)=1 \\ 前面几项:x(1) = 1; \quad x(2) = 2x(1)+1 = 3; \quad x(3) = 2x(2)+1 = 7; \quad \dots \\ 数学归纳法总结规律(通项):x(n) = 2^n - 1, n=1,2,3\dots 递推式:x(n)=2x(n−1)+1,其中n>1x(1)=1前面几项:x(1)=1;x(2)=2x(1)+1=3;x(3)=2x(2)+1=7;…数学归纳法总结规律(通项):x(n)=2n−1,n=1,2,3…
反向替换法(backward substutution):
递推式: x ( n ) = x ( n − 1 ) + n , 其中 n > 0 用 n − 1 代替原式的 n : x ( n − 1 ) = x ( n − 2 ) + n − 1 原式即为: x ( n ) = x ( n − 2 ) + ( n − 1 ) + n … x ( n ) = x ( n − i ) + ( n − i + 1 ) + ( n − i + 2 ) + ⋯ + n 初始条件为 0 ,则需要 n − i = 0 ,即 i = n : x ( n ) = x ( 0 ) + 1 + 2 + ⋯ + n = n ( n + 1 ) 2 递推式:x(n) = x(n-1) + n ,其中n > 0 \\ 用n-1代替原式的n:x(n-1) = x(n-2) + n-1 \\ 原式即为:x(n) = x(n-2) + (n-1) + n \\ \dots \\ x(n) = x(n-i)+(n-i+1)+(n-i+2)+\dots+n \\ 初始条件为0,则需要n-i=0,即i=n:x(n)=x(0)+1+2+\dots+n = \frac{n(n+1)}{2} 递推式:x(n)=x(n−1)+n,其中n>0用n−1代替原式的n:x(n−1)=x(n−2)+n−1原式即为:x(n)=x(n−2)+(n−1)+n…x(n)=x(n−i)+(n−i+1)+(n−i+2)+⋯+n初始条件为0,则需要n−i=0,即i=n:x(n)=x(0)+1+2+⋯+n=2n(n+1)​

通用方案:

  • 输入规模确定;
  • 找出算法的基本操作;
  • 检查基本操作的执行次数,建立递推关系式以及相应初始条件;
  • 解递推式,确定解的增长次数。

4.算法分析中常见的递推类型

  • 减一法(decrease-by-one)

如对 n ! n! n!递归求值和插入排序,其时间效率递推方程一般形式如下:
T ( n ) = T ( n − 1 ) + f ( n ) , 其中 f ( n ) 代表把实例简化成一个更小的实例并将其扩展为更大实例的解所需时间 应用反向替换法: T ( n ) = T ( n − 1 ) + f ( n ) = T ( n − 2 ) + f ( n − 1 ) + f ( n ) = … = T ( 0 ) + ∑ j = 1 n f ( j ) T(n) = T(n-1)+f(n),其中f(n)代表把实例简化成一个更小的实例并将其扩展为更大实例的解所需时间 \\ 应用反向替换法: \begin{aligned} T(n) &= T(n-1) + f(n) \\ &= T(n-2) + f(n-1) + f(n) \\ &= \dots \\ &= T(0)+\sum_{j=1}^{n}f(j) \end{aligned} T(n)=T(n−1)+f(n),其中f(n)代表把实例简化成一个更小的实例并将其扩展为更大实例的解所需时间应用反向替换法:T(n)​=T(n−1)+f(n)=T(n−2)+f(n−1)+f(n)=…=T(0)+j=1∑n​f(j)​

  • 减常因子(decrease-by-constant-factor)

将规模为 n n n的一个实例化简为一个规模为 n b ( b > 1 ) \frac{n}{b}(b>1) bn​(b>1)的实例求解:如折半查找、平方求幂、俄式乘法和假币问题。
递推式: T ( n ) = T ( n b ) + f ( n ) , 其中 b > 1 按照 n = b k 求解: T ( b k ) = T ( b k − 1 ) + f ( b k ) = T ( b k − 2 ) + f ( b k − 1 ) + f ( b k ) = … = T ( 1 ) + ∑ j = 1 k f ( b j ) 若 f ( n ) = 1 : ∑ j = 1 k f ( b j ) = k = log ⁡ b n 若 f ( n ) = n : ∑ j = 1 k f ( b j ) = ∑ j = 1 k b j = b b k − 1 b − 1 = b n − 1 b − 1 递推式:T(n)=T(\frac{n}{b})+f(n) ,其中b>1 \\ 按照n=b^k求解:\begin{aligned} T(b^k) &= T(b^{k-1})+f(b^k) \\ &= T(b^{k-2})+f(b^{k-1})+f(b^k) \\ &= \dots \\ &= T(1)+\sum_{j=1}^{k}f(b^{j}) \end{aligned} \\ 若f(n)=1:\sum_{j=1}^{k}f(b^j) = k = \log_{b}n \\ 若f(n)=n:\sum_{j=1}^{k}f(b^j) = \sum_{j=1}^{k} b^j = b \frac{b^k - 1}{b-1} = b \frac{n-1}{b-1} 递推式:T(n)=T(bn​)+f(n),其中b>1按照n=bk求解:T(bk)​=T(bk−1)+f(bk)=T(bk−2)+f(bk−1)+f(bk)=…=T(1)+j=1∑k​f(bj)​若f(n)=1:j=1∑k​f(bj)=k=logb​n若f(n)=n:j=1∑k​f(bj)=j=1∑k​bj=bb−1bk−1​=bb−1n−1​

  • 分治法(divide-and-conquer)

将给定规模为n的实例化简为若干个规模为 n b ( b > 1 ) \frac{n}{b}(b>1) bn​(b>1)的实例求解,a个实例(子问题个数)都递归求解;若有必要,再将较小实例的解合并成给定实例的一个解。
递推式: T ( n ) = a T ( n b ) = f ( n ) , 其中 a ≥ 1 , b ≥ 2 ( 若 a = 1 , 属于减常因子而非分治法; f ( n ) 表示合并消耗时间 ) T ( b k ) = a T ( b k − 1 ) = f ( b k ) = a [ a T ( b k − 2 ) + f ( b k − 1 ) ] + f ( b k ) = … = a k [ T ( 1 ) + ∑ j = 1 k f ( b j ) a j ] 其中 a k = a l o g b n = n l o g b a 递推式:T(n)=aT(\frac{n}{b})=f(n),其中a≥1,b≥2 \quad (若a=1,属于减常因子而非分治法;f(n)表示合并消耗时间) \\ \begin{aligned} T(b^k) &= aT(b^{k-1}) = f(b^k) \\ &= a[aT(b^{k-2})+f(b^{k-1})]+f(b^k) \\ &= \dots \\ &= a^k[T(1)+\sum_{j=1}^{k}\frac{f(b^{j})}{a^j}] \end{aligned} \qquad 其中a^k = a^{log_{b}n} = n^{log_{b}a}\\ 递推式:T(n)=aT(bn​)=f(n),其中a≥1,b≥2(若a=1,属于减常因子而非分治法;f(n)表示合并消耗时间)T(bk)​=aT(bk−1)=f(bk)=a[aT(bk−2)+f(bk−1)]+f(bk)=…=ak[T(1)+j=1∑k​ajf(bj)​]​其中ak=alogb​n=nlogb​a

  • 主定理(对 O O O和 Ω \Omega Ω同样成立)

设 T ( n ) 是一个最终非递减函数,且满足递推式 T ( n ) = a T ( n b ) + f ( n ) 其中 n = b k , k = 1 , 2 … T ( 1 ) = c a ≥ 1 , b ≥ 2 , c > 0 如果 f ( n ) ∈ Θ ( n d ) , d ≥ 0 ,那么 T ( n ) ∈ { Θ ( n d ) a < b d Θ ( n d log ⁡ n ) a = b d Θ ( n log ⁡ b a ) a > b d 设T(n)是一个最终非递减函数,且满足递推式 \\ T(n)=aT(\frac{n}{b})+f(n) \qquad 其中n=b^k,k=1,2\dots \\ T(1)=c \qquad a≥1,b≥2,c>0 \\ 如果f(n) \in \Theta(n^d),d≥0,那么 T(n) \in \begin{cases} \Theta(n^d) \qquad a<b^d \\ \Theta(n^d\log{n}) \qquad a=b^d \\ \Theta(n^{\log_b{a}}) \qquad a>b^d \end{cases} 设T(n)是一个最终非递减函数,且满足递推式T(n)=aT(bn​)+f(n)其中n=bk,k=1,2…T(1)=ca≥1,b≥2,c>0如果f(n)∈Θ(nd),d≥0,那么T(n)∈⎩ ⎨ ⎧​Θ(nd)a<bdΘ(ndlogn)a=bdΘ(nlogb​a)a>bd​

Chapter 3 蛮力法

引例: a n = a × a × ⋯ × a ⏟ n 次 a^n=\underbrace{a\times{a}\times \dots \times{a}}_{n次} an=n次 a×a×⋯×a​​

1.选择排序和冒泡排序

选择排序

算法思想:拆分顺序表为【有序部分|待排序部分】,每轮排序确定一个元素的最终位置。首先在未排序序列中找到最小(大)元素,存放到有序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

算法分析:最差,最好及平均情况下时间复杂度都为 O ( n 2 ) O(n^2) O(n2),键的交换次数为 O ( n ) O(n) O(n),准确来说是 n − 1 n-1 n−1次。

//选择排序
void SelectSort(char s[],int len)
{
	int i,j,min;
	for(i=0;i<len-1;i++){  // 0 to len-2
        min = i;
        for (j=i+1;j<len;j++){
            if(s[min]>s[j]) min = j;
        }
        if(min != i){
            char t=s[min];
            s[min]=s[i];
            s[i]=t;
        }
    }
}

冒泡排序

算法思想:每轮排序确定一个元素的最终位置。对相邻的元素进行两两比较,顺序相反则进行交换,这样,每一趟会将最小或最大的元素“浮”到顶端,最终达到完全有序。

算法分析:最差,平均情况下时间复杂度都为 O ( n 2 ) O(n^2) O(n2),最好情况下为 O ( n ) O(n) O(n)。

public int[] bubbleSort(int[] arr) {
    for (int i = 0; i < arr.length-1; i++) { //比较趟数
        for (int j = 0; j < arr.length - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j + 1];
                arr[j + 1] = arr[j];
                arr[j] = temp;
            }
        }
    }
    return arr;
}

2.顺序查找和蛮力字符串匹配

顺序查找

算法思想:将给定列表中的连续元素和给定的查找键进行比较,直到遇到一个匹配的元素(成功查找);或者在遇到匹配元素之前就遍历了整个列表(查找失败)。

算法分析:最坏和平均时间复杂度都为O(n);最好情况是待查找元素在数组第一个位置,为O(1)。

int SequentialSearch(int *A,int n,int k){
// 输入:数组A[0...n-1],待查找元素
// 输出:若查找成功,返回对应数组下标;查找失败返回-1
	int i = 0;
	while(A[i] != k & i < n){
		i += 1;}
	if(i < n){return i;}
	else{return -1;}
}

蛮力字符串匹配

算法思想:给定一个n个字符组成的串,称为文本;一个 m ( m ≤ n ) m(m≤n) m(m≤n)个字符的串,称为模式。

​ 将模式对准文本的前m个字符,然后从左到右匹配每一对相应的字符,直到m对字符全部匹配(算法就可停止了);或者遇到一对不匹配的字符。m对字符没有全部匹配时,模式向右移一位,然后从模式的第一个字符开始,继续把模式和文本中对应字符进行比较。

​ 在文本中,最后一轮子串匹配的起始位置是n-m(文本位置的下标是0到n-1),在这个位置以后,再也没有足够的字符可以匹配整个模式,算法可以停止。

算法分析:最好情况下,比较模式串的长度m,匹配成功,时间复杂度 O ( m ) O(m) O(m);最坏情况下,移动模式之前,算法可能会做足m次比较,而 n − m + 1 n-m+1 n−m+1次尝试的每一次都可能比较m次然后匹配失败,因此时间复杂度为 ∑ i = 0 n − m ∑ j = 0 m − 1 1 = ∑ i = 0 n − m = m ( n − m + 1 ) = m n − m 2 + m = O ( m n ) \sum_{i=0}^{n-m}\sum_{j=0}^{m-1} 1 = \sum_{i=0}^{n-m} = m(n-m+1) = mn-m^2+m=O(mn) ∑i=0n−m​∑j=0m−1​1=∑i=0n−m​=m(n−m+1)=mn−m2+m=O(mn)。

BruteForceStringMatch(char *T,int n,char *P,int m){
// 输入:一个n个字符的数组T[0...n-1] 代表文本;一个m个字符的数组P[0...m-1] 代表模式
// 输出:如果查找成功,返回文本的第一个匹配子串中第一个字符的位置,否则返回-1
    int i,j;
    for(i=0; i<n-m; i++){
        j=0;
        while(j<m&&P[j]==T[i+j]){ //字符串比较
            j++;
            if(j==m){return i+1;}//返回下标+1的值
        }
    }
    return -1;
}

KMP算法(时空权衡,预处理存储信息)

​ 蛮力字符串匹配算法的缺点是,当文本的某些子串与模式串能部分匹配时,主串和模式串的扫描指针经常回溯,导致时间开销增加。KMP算法是对蛮力字符串匹配算法的改进

算法思想:通过next数组的方式确定模式串失配时指针j回溯的位置。如果某个字符匹配成功,模式串首字符的位置保持不动,仅仅是i++、j++;如果匹配失配,i 不变(即 i 不回溯),模式串会跳过匹配过的next [j]个字符。

计算模式串(“abcab”)后缀的最大公共长度:

模式串子串前缀后缀最大公共字符串最大公共字符串长度
anullnullnull0
ababnull0
abca,abbc,cnull0
abcaa,ab,abcbca,ca,aa1
abcaba,ab,abc,abcabcab,cab,ab,bab2

next数组:

序号j01234
模式串abcab
最大匹配00012
next[j]-10001
//求模式串P的next数组
void cal_next(string &str, vector &next){	 
    int k =-1 , j = 0;
    next[0] = -1;
    len = str.size();
    while (j < len - 1)
    {	if (k == -1 || str[j] == str[k])
        {   ++k;++j;
            next[j] = k;//表示第j个字符有k个匹配(“最大长度值” 整体向右移动一位,然后初始值赋为-1)
        }   else k = next[k];//往前回溯
    }
}

// KMP算法
vector<int> KMP(string &str1, string &str2, vector<int> &next){	
    vector<int> vec;
    cal_next(str2, next);
    int i = 0;//i是str1的下标
    int j = 0;//j是str2的下标
    int str1_size = str1.size();
    int str2_size = str2.size();
    while (i < str1_size && j < str2_size)
    {   //如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),
        //都令i++,j++. 注意:这里判断顺序不能调换!
        if (j == -1 || str1[i] == str2[j])
        {  ++i;++j;
        }else
            j = next[j];//当前字符匹配失败,直接从str[j]开始比较,i的位置不变
        if (j == str2_size)//匹配成功
        {    vec.push_back(i - j);//记录下完全匹配最开始的位置
            j = -1;//重置
        }
    }
    return vec;
}

算法分析:最好情况下,比较模式串的长度m,匹配成功。O(m);最坏情况下,当模式串首字符位于 i − j i-j i−j的位置时才匹配成功,算法结束。所以,当文本串长度为n,模式串长度为m,那么匹配过程的时间复杂度为O(n),算上计算next数组的O(m)时间,因此整体时间复杂度为 O ( m + n ) O(m+n) O(m+n)。

3.最近对和凸包问题

最近对

问题描述:假设所讨论的点是以标准笛卡儿坐标(二维)形式 ( x , y ) (x, y) (x,y)给出的。因此,在两个点 P i = ( x i , y i ) P_i=(x_i, y_i) Pi​=(xi​,yi​)和 P j = ( x j , y j ) P_j=(x_j, y_j) Pj​=(xj​,yj​)之间的距离是标准的欧几里德距离: d = ( x i − x j ) 2 + ( y i − y j ) 2 d=\sqrt{(x_i - x_j)^2 + (y_i - y_j)^2} d=(xi​−xj​)2+(yi​−yj​)2 ​。

求解过程:

  1. 分别计算每一对点之间的距离;
  2. 然后找出距离最小的那一对,为了避免对同一对点计算两次距离,只考虑 i < j i<j i<j的那些点对 ( P i , P j ) (P_i, P_j) (Pi​,Pj​)。

算法分析:输入规模为点的个数 n n n,基本运算为求平方、求和、比较;时间复杂度为 O ( n 2 ) O(n^2) O(n2)。

int BruteForceClosestPoints(int n, int x[ ], int y[ ], int &index1, int &index2){
    //输入:一个n(n>=2)个点的列表  p,p1=(x1,y1)  ,…,pn=(xn,yn)   
    //输出:两个最近点的距离 
    minDist=+∞;  
    for (i=1; i<n; i++)    
        for (j=i+1; j<=n; j++){    
            d=(x[i]-x[j])* (x[i]-x[j])+(y[i]-y[j])* (y[i]-y[j]);  
            if (d<minDist) {     
                minDist=d;       
                index1=i;         
                index2=j;       
            }    
        }   
    return  minDist;}

凸包

凸集合:对于平面上的一个点的有限集合,如果以集合中任意两点P和Q为端点的线段上的点都属于该集合,则称该集合是凸集合。

**凸包(convex-hull):**一个点集S的凸包是包含S的最小凸集合,其中,最小是指S的凸包一定是所有包含S的凸集合的子集。 对于平面上n个点的集合S,它的凸包就是包含所有这些点(或者在内部,或者在边界上)的最小凸多边形。

凸包问题是为一个具有n个点的集合构造凸多边形的问题。为了解决凸包问题,需要找出凸多边形的顶点,这样的点称为极点。一个凸集合的极点应该具有这样性质:对于任何以凸集合中的点为端点的线段来说,极点不是这种线段中的点。

convex_hull

**蛮力法求解凸包问题的基本思想:**对于一个由n个点构成的集合S中的两个点 P i P_i Pi​和 P j P_j Pj​,当且仅当该集合中的其他点都位于穿过这两点的直线的同一边时(假定不存在三点同线的情况),他们的连线是该集合凸包边界的一部分。对每一对顶点都检验一遍后,满足条件的线段构成了该凸包的边界。

​ 在平面上,穿过两个点 ( x 1 , y 1 ) (x_1, y_1) (x1​,y1​)和 ( x 2 , y 2 ) (x_2, y_2) (x2​,y2​)的直线是由方程 a x + b y = c ( 其中, a = y 2 − y 1 , b = x 1 − x 2 , c = x 1 y 2 − y 1 x 2 ) ax + by = c (其中,a=y_2-y_1, b=x_1-x_2, c=x_1y_2-y_1x_2) ax+by=c(其中,a=y2​−y1​,b=x1​−x2​,c=x1​y2​−y1​x2​)定义的。

​ 这样一条直线把平面分成两个半平面:其中一个半平面中的点都满足 a x + b y > c ax + by>c ax+by>c,另一个半平面中的点都满足 a x + b y < c ax + by<c ax+by<c,因此,为了检验这些点是否位于这条直线的同一边,可以简单地把每个点代入方程 a x + b y = c ax + by = c ax+by=c,检验这些表达式的符号是否相同。

4.穷举查找

旅行商问题

哈密顿回路
hamilton

蛮力法求解算法思想:对于给定的无向图 G = ( V , E ) G=(V, E) G=(V,E),首先生成图中所有顶点的排列对象 ( v i 1 , v i 2 , … , v i n ) (v_{i1}, v_{i2}, …, v_{in}) (vi1​,vi2​,…,vin​),然后依次考察每个排列对象是否满足以下两个条件:

  • 相邻顶点之间存在边,即 ( v i j , v i j + 1 ) ∈ E ( 1 ≤ j ≤ n − 1 ) (v_{ij}, v_{ij+1})∈E(1≤j≤n-1) (vij​,vij+1​)∈E(1≤j≤n−1)
  • 最后一个顶点和第一个顶点之间存在边,即 ( v i n , v i 1 ) ∈ E (v_in, v_i1)∈E (vi​n,vi​1)∈E,满足这两个条件的回路就是哈密顿回路。

hamilton_2

算法分析:最坏情况下需要考察所有顶点的排列对象,其时间复杂性为 O ( n ! ) O(n!) O(n!)。

背包问题

问题描述:给定n个重量为 w 1 , w 2 , … , w n w_1,w_2,\dots,w_n w1​,w2​,…,wn​,价值为 v 1 , v 2 , … , v n v_1,v_2,\dots,v_n v1​,v2​,…,vn​的物品和承重为 W W W的背包,求这些物品中最有价值的子集,且要能够装到背包中。

0-1背包问题建模

bag
bag2

算法分析:对于一个具有n个元素的集合,其子集数量是 2 n 2^n 2n,所以,不论生成子集的算法效率有多高,蛮力法都会导致一个 Ω ( 2 n ) Ω(2^n) Ω(2n)的算法。

分配问题

问题描述:假设有n个任务需要分配给n个人执行,每个任务只分配给一个人,每个人只分配一个任务,且第j个任务分配给第i个人的成本是 C [ i , j ] ( 1 ≤ i , j ≤ n ) C[i, j](1≤i , j≤n) C[i,j](1≤i,j≤n),任务分配问题要求找出总成本最小的分配方案。

5.图的深度优先查找和广度优先查找

DFS

基本思想:类似树的先序遍历过程。

  • 在访问图中某一起始顶点 v 后,由 v 出发,访问它的任一邻接顶点 w 1 w_1 w1​;
  • 再从 w 1 w_1 w1​ 出发,访问与 w1邻接但还未被访问过的顶点 w 2 w_2 w2​;
  • 然后再从 w 2 w_2 w2​ 出发,进行类似的访问,…
  • 如此进行下去,直至到达所有的邻接顶点都被访问过的顶点 u 为止;
  • 接着,退回一步,退到前一次刚访问过的顶点,看是否还有其它没有被访问的邻接顶点。
    • 如果有,则访问此顶点,之后再从此顶点出发,进行与前述类似的访问;
    • 如果没有,就再退回一步进行搜索。重复上述过程,直到连通图中所有顶点都被访问过为止。
void DFS(AMGraph G, int v){        		//图G为邻接矩阵类型   
    cout<<v;  visited[v] = true;  		//访问第v个顶点   
    for(w = 0; w< G.vexnum; w++)  	//依次检查邻接矩阵v所在的行    
        if((G.arcs[v][w]!=0)&& (!visited[w]))    
            DFS(G, w);       //w是v的邻接点,如果w未访问,则递归调用DFS
}

void DFS(ALGraph G, int v){        		//图G为邻接表类型 
    cout<<v;  visited[v] = true;    		//访问第v个顶点    
    p= G.vertices[v].firstarc;     //p指向v的边链表的第一个边结点  
    while(p!=NULL){              	//边结点非空    
        w=p->adjvex;               	//表示w是v的邻接点     
        if(!visited[w])  DFS(G, w); 	//如果w未访问,则递归调用DFS    
        p=p->nextarc;                	//p指向下一个边结点 
    } 
} 

算法分析:用邻接矩阵来表示图,遍历图中每一个顶点都要从头扫描该顶点所在行,时间复杂度为 O ( n 2 ) O(n^2) O(n2);
用邻接表来表示图,虽然有 2 e 2e 2e 个表结点,但只需扫描 e 个结点即可完成遍历,加上访问 n个头结点的时间,时间复杂度为 O ( n + e ) O(n+e) O(n+e)。

总结:稠密图适于在邻接矩阵上进行深度遍历;稀疏图适于在邻接表上进行深度遍历。

BFS

基本思想:类似树的层次遍历过程。

  • 在访问了起始点v之后,依次访问 v的邻接点;
  • 然后再依次访问这些顶点中未被访问过的邻接点;
  • 直到所有顶点都被访问过为止。

算法描述:

(1)从图中某个顶点v出发,访问v,并置visited[v]的值为true,然后将v进队。
(2)只要队列不空,则重复下述处理:
① 队头顶点u出队。
② 依次检查u的所有邻接点w,如果visited[w]的值为false,则访问w,并置visited[w]的值为true,然后将w进队。

**注意:**广度优先搜索是一种分层的搜索过程,每向前走一步可能访问一批顶点,不像深度优先搜索那样有回退的情况。因此,广度优先搜索不是一个递归的过程,其算法也不是递归的。

void BFS (Graph G, int v){ //按广度优先非递归遍历连通图G  
    cout<<v; visited[v] = true;     		//访问第v个顶点  
    InitQueue(Q);              			//辅助队列Q初始化,置空     
    EnQueue(Q, v);            			//v进队   
    while(!QueueEmpty(Q)){   		//队列非空     
        DeQueue(Q, u);        		 //队头元素出队并置为u     
        for(w = FirstAdjVex(G, u); w>=0; w = NextAdjVex(G, u, w))    
            if(!visited[w]){               	//w为u的尚未访问的邻接顶点      
                cout<<w; visited[w] = true;	EnQueue(Q, w); //w进队     
            }//if   
    }//while
}//BFS 

算法分析:如果使用邻接矩阵,则BFS对于每一个被访问到的顶点,都要循环检测矩阵中的整整一行( n 个元素),总的时间代价为 O ( n 2 ) O(n^2) O(n2);
用邻接表来表示图,虽然有 2 e 2e 2e 个表结点,但只需扫描 e 个结点即可完成遍历,加上访问 n个头结点的时间,时间复杂度为 O ( n + e ) O(n+e) O(n+e)。

Chapter 4 减治法

减治(decrease-and-conquer)技术利用了规模为n的原问题的解与较小规模的子问题的解之间具有关系:
(1)原问题的解只存在于其中一个较小规模的子问题中(如二叉树查找);
(2)原问题的解与其中一个较小规模的解之间存在某种对应关系。
所以,只需求解其中一个较小规模的子问题就可以得到原问题的解。

自底向上,由“少”到“多”;自顶向下,递归。

1.插入排序、希尔排序和拓扑排序

插入排序(减一)

算法思想:每一步将一个待排序的记录,插入到前面已经排好序的有序序列中去,直到插完所有元素为止。

算法分析:最差,平均情况下时间复杂度都为 O ( n 2 ) O(n^2) O(n2),最好情况下为 O ( n ) O(n) O(n)。

//直接插入排序
void InSort(char s[],int len){
    int i,j;    // 数组数据整体右移 空出s[0]留作监视哨  
    for(i=2;i<=len;i++)    //数组下标从2开始比较,s[0]做监视哨,s[1]一个数据认为已有序   
    {        s[0]=s[i];    //给监视哨赋值    
     j=i-1;    //要比较数组的最左边位     
     while(s[0]<s[j] && j>0)    
     {          
         s[j+1]=s[j];    //数据右移     
         j--;     
     }      
     s[j+1]=s[0];    //在确定的位置插入s[i]  
    }
}

希尔排序

算法思想:把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。

算法分析: 希尔排序的核心在于间隔序列的设定。既可以提前设定好间隔序列,也可以动态的定义间隔序列。

//希尔排序
#include<stdio.h>
#include<stdlib.h>
void ShellSort(char A[],int len)
{
	int i,j;
	int d;//d为增量
	char temp;//temp为临时变量
	for(d=len/2;d>0;d /= 2)
	{
		//增量为d的直接插入排序 
		for(i=d;i<len;i++)
		{
			char temp = A[i];
			for(j=i;j>=d && A[j-d]>temp;j-=d)
				A[j] = A[j-d];
			A[j] = temp;
		}
		printf("增量为%d的shellsort:",d);
		for(int k=0;k<len;k++){
			printf("%c",A[k]);
		}
		printf("\n");
	} 
}

void main(){
	int len = 17;
	char a[len];
	a[0]='S';a[1]='H';a[2]='E';a[3]='L';a[4]='L';a[5]='S';a[6]='O';a[7]='R';a[8]='T';
	a[9]='I';a[10]='S';a[11]='U';a[12]='S';a[13]='E';a[14]='F';a[15]='U';a[16]='L';
	ShellSort(a,len);
}

拓扑排序

基于DFS的应用以及减一技术。

前提:拓扑排序的图须为有向无环图。

2.生成组合对象的算法

生成排列

算法思想:假设已经生成了所有 ( n − 1 ) ! (n-1)! (n−1)!个排列,可以把n插入到 n − 1 n-1 n−1个元素的每一种排列中的n个位置中去,来得到问题规模为n的所有排列。按照这种方式生成的所有排列都是独一无二的,并且他们的总数应该是 n ( n − 1 ) ! = n ! n(n-1)!=n! n(n−1)!=n!。

对 { 1 , 2 , 3 } \{1,2,3\} {1,2,3}生成排列的过程(自底向上,满足最小变化要求)

开始1
插入21221
插入3123132312321231213

Johnson-Trotter算法

JohnsonTrotter(n)
    //输入:一个正整数n
    //输出:{1,…,n}的所有排列将第一个排列初始化为(←1)(←2)…(←n)  箭头在上
    while 存在一个移动元素 do    
        求最大的移动元素k  
        把k和它箭头指向的相邻元素互换    
        调转所有大于k的元素的方向   
        将新排列添加到列表中

算法分析:算法时间复杂性为 O ( n ! ) O(n!) O(n!),也就是说和排列对象的数量成正比。这不是算法的问题,而是该问题本身的复杂性。

递归算法生成排列对象

void print_permutation(int n,int A[],int cur){ 
    if(cur==n){     
        for (i=0; i<n; i++) 
            printf(“%d”,A[i]);    //递归的边界   
        printf(“\n”);    
        else     
            for(i=1;i<=n;i++){     //尝试在A[cur]中填各种整数I       
                int ok=1;          
                for (j=0; j<cur; j++)            
                    if(A[j]==cur) ok=0;  //如果i已经在A[0]-A[cur-1]出现过,则不能再选      
                if(ok){ 
                    A[cur]=i;            
                    print_permutation(n,A,cur+1); //递归调用             
                      }       
            }
}

生成子集

算法思想:n个元素的集合 A = { a 1 , a 2 , … … , a n } A=\{a1, a2,……, an\} A={a1,a2,……,an}的所有 2 n 2n 2n个子集和长度为n的所有 2 n 2n 2n个比特串之间的一一对应关系;每一个子集指定一个比特串 b 1 b 2 … b n b_1b_2…b_n b1​b2​…bn​,如果 a i a_i ai​属于该子集,则 b i = 1 b_i=1 bi​=1;如果 a i a_i ai​不属于该子集,则 b i = 0 ( 1 ≤ i ≤ n ) b_i=0(1≤i≤n) bi​=0(1≤i≤n)。

比特串000001010011100101110111
子集 ∅ \emptyset ∅ { a 3 } \{a_3\} {a3​} { a 2 } \{a_2\} {a2​} { a 2 , a 3 } \{a_2,a_3\} {a2​,a3​} { a 1 } \{a_1\} {a1​} { a 1 , a 3 } \{a_1,a_3\} {a1​,a3​} { a 1 , a 2 } \{a_1,a_2\} {a1​,a2​} { a 1 , a 2 , a 3 } \{a_1,a_2,a_3\} {a1​,a2​,a3​}

二进制反射格雷码(binary reflected Gray code)

BRGC(n)//递归生成n位的二进制反射格雷码  n位元格雷码是基于n-1位元格雷码产生的
    //输入:一个正整数n
    //输出:所有长度为n的格雷码位串列表   
    if n=1,表L中包含位串0和位串1  
        else 调用BRGC(n-1)生成长度为n-1的位串列表L1  
            把表L1倒序后复制给表L2    
            把0加到表L1中的每个位串前面   
            把1加到表L2中的每个位串前面   
            把表L2添加到表L1后面得到表L1  
            return L

算法分析:算法生成了 2 n 2^n 2n个位串,并且全部位串都是不同的。应用在数字信号传输中,可降低误差影响。二进制反射格雷码是循环的(它的最后一个位串与第一个位串只相差一位)。

递归算法实现二进制反射格雷码的生成

/** 递归生成二进制格雷码  * 思路:1、获得n-1位生成格雷码的数组 
*      2、由于n位生成的格雷码位数是n-1的两倍,故只要在n为格雷码的前半部分加0,后半部分加1即可。 
* @param n 格雷码的位数 
* @return 生成的格雷码数组 */  
public static String[] GrayCode(int n) {    //数组的大小是2的n次方,因为n位的格雷码有2的n次方种排列   
    String[] grayCodeArr = new String[(int)Math.pow(2, n)];     
    if(n < 1){ System.out.println("你输入的格雷码位数有误!"); }       
    if(1 == n){         
        grayCodeArr[0] = "0";
        grayCodeArr[1] = "1";     
        return grayCodeArr;  
    }        
    //n-1 位格雷码的生成方式    
    String[] before = GrayCode(n-1);    
    for(int i = 0 ; i < before.length ; i++){     
        grayCodeArr[i] = "0" + before[i]; // 从前往后给每个位串之前拼接"0"  
        grayCodeArr[grayCodeArr.length -1 - i] = "1" + before[i];  // 从后往前给每个位串之前拼接"1"     
    }      
    return grayCodeArr;   
}

非递归算法实现二进制反射格雷码的生成

public static String[] GrayCode2(int n){ 
    int num = (int)Math.pow(2, n);//根据输入的整数,计算出此Gray序列大小 
    String[] s1 = {"0","1"};//第一个Gray序列   
    if(n < 1){ 
        System.out.println("你输入的格雷码位数有误!"); 
    }    
    for(int i=2;i<=n;i++){//循环根据第一个Gray序列,来一个一个的求     
        int p = (int)Math.pow(2, i);//到了第几个的时候,来计算出此Gray序列大小     
        String[] si = new String[p];     
        for(int j=0;j<p;j++){//循环根据某个Gray序列,来一个一个的求此序列     
            if(j<(p/2)){         
                si[j] = "0" + s1[j];//原始序列前面加上"0"      
            }else{       
                si[j] = "1" + s1[p-j-1];//原始序列反序,前面加上"1"     
            }     
        }     
        s1 = si;//把求得的si,附给s1,以便求下一个Gray序列  
    }  
    return s1;
}

3.减常因子算法

折半查找(常因子为2,减半)和排序

算法思想:折半查找通过比较待查找键k和有序表中间元素A[m]来完成查找工作,如果k==A[m],则查找成功,算法结束;否则当k>A[m],对有序表的后半部分执行折半查找操作;当k<A[m],对有序表的前半段执行折半查找操作。

算法分析:当 n > 1 n>1 n>1时, C w o r s t ( n ) = C w o r s t ( ⌊ n / 2 ⌋ ) + 1 , C w o r s t ( 1 ) = 1 C_{worst}(n)=C_{worst}(\lfloor n/2 \rfloor)+1, C_{worst}(1)=1 Cworst​(n)=Cworst​(⌊n/2⌋)+1,Cworst​(1)=1

所以当 n = 2 k n=2^k n=2k时, C w o r s t = ⌊ l o g 2 n ⌋ + 1 C_{worst}=\lfloor log_2{n} \rfloor+1 Cworst​=⌊log2​n⌋+1;平均时间复杂度为 C a v g ≈ log ⁡ 2 n C_{avg} ≈ \log_{2}{n} Cavg​≈log2​n。

// 减治法的应用:折半查找
void BinarySearch(A[], x, low, high){    //A为有序数组 x为待查找元素,low和high为数组上下界  
    if low > high return -1; 
    else   
        int mid = (low+high)/2;//下取整   
    if x = A[mid]      
        return mid;  
    else if x < A[mid] // 递归左半边和右半边    
        return BinarySearch(A,x,l,mid-1);  
    else return BinarySearch(A,x,mid+1,high)
}
void Half_InsertSort(SqList *L){
    //折半插入排序 初始无序仍为O(n^2) 稳定
    //仅减少了比较的次数O(nlogn)
    int i,j,low,high,mid;	
    for(i=L->Length;i>=0;i--) //将线性表全部往后移一位 空出第一位做监视哨	
        L->data[i]=L->data[i-1];
    for(i=2;i<=L->Length;i++){ //依次将L[2]~L[Length]插入到前面已排序序列	
        L->data[0]=L->data[i];	
        low=1;high=i-1;	
        while(low<=high){//折半查找	
            mid=(high+low)/2;		
            if(L->data[mid]>L->data[0]) high=mid-1;		
            else low=mid+1;	
        }		
        for(j=i-1;j>=high+1;--j)	
            L->data[j+1]=L->data[j];  //后移	
        L->data[high+1]=L->data[0];		
    }//显示	
    for(i=1;i<=L->Length;i++)	
        printf("%3d",L->data[i]);
    printf("\n");
}

假币问题

问题描述:在n枚外观相同的硬币中,有一枚是假币,并且已知假币较轻。可以通过一架天平来任意比较两组硬币,从而得知两组硬币的重量是否相同,或者哪一组更轻一些,但不知道轻多少,假币问题是要求设计一个高效的算法来检测出这枚假币。

​ 在假币问题中,每次用天平比较后,只需解决一个规模减半的问题。该算法在最坏情况下的时间性能有这样一个递推式: T ( n ) = { 0 n = 1 T ( n 2 ) + 1 n > 1 T(n)=\begin{cases}0 \qquad n=1 \\ T(\frac{n}{2})+1 \qquad n>1 \end{cases} T(n)={0n=1T(2n​)+1n>1​,可得 T ( n ) = O ( log ⁡ 2 n ) T(n)=O(\log_2{n}) T(n)=O(log2​n)。

改进算法思想:考虑不是把硬币分成两组,而是分成三组,前两组有 ⌈ n 3 ⌉ \lceil{\frac{n}{3}}\rceil ⌈3n​⌉ 组硬币,其余的硬币作为第三组,将前两组硬币放到天平上,如果他们的重量相同,则假币一定在第三组中,用同样的方法对第三组进行处理;如果前两组的重量不同,则假币一定在较轻的那一组中,用同样的方法对较轻的那组硬币进行处理。 T ( n ) = O ( log ⁡ 3 n ) T(n)=O(\log_3{n}) T(n)=O(log3​n)。

俄式乘法

问题描述:假设n和m是两个正整数,我们要计算它们的乘积,同时,我们用n的值作为实例规模的度量标准,则可以得到递推公式: n × m = { m n = 1 n 2 × 2 m n 为偶数 n − 1 2 × 2 m + m n 为奇数 n \times m=\begin{cases}m \qquad n=1 \\ \frac{n}{2} \times 2m \qquad n为偶数 \\ \frac{n-1}{2} \times 2m + m \qquad n为奇数 \end{cases} n×m=⎩ ⎨ ⎧​mn=12n​×2mn为偶数2n−1​×2m+mn为奇数​。

4.减可变规模算法

计算中值和选择问题

问题描述:从给定的集合 L 中选择第 i 小的元素,不妨设 L 为 n 个不等的实数。

  • 输入:集合L(含n个不等的实数);

  • 输出:L中第i小元素

    • i=1, 称为最小元素;

    • i=n,称为最大元素;

    • i=n-1,称为第二大元素;

位置处在中间的元素,称为中位元素

  • 当 n为奇数时,中位数只有1个, i = ( n + 1 ) / 2 i=(n+1)/2 i=(n+1)/2;
  • 当 n为偶数时,中位数有2个, i = n / 2 , n / 2 + 1 i=n/2, n/2+1 i=n/2,n/2+1, 也可以规定其中的一个。
找第k小的元素
#include <iostream>
#define N 1000
using namespace std;
int Kminselect(int a[],int s,int t,int k){ 
    int i=s,j=t,temp;   
    if(s<t){    
        temp=a[s];     
        // 快速排序思想   
        while(i!=j){      
            while(j>i && a[j]>=temp) j--; //从右边向中间扫描,直到a[j]<temp       
            a[i]=a[j];                    //a[j]前移到a[i]的位置     
            while(i<j && a[i]<=temp) i++; //从左边向中间扫描,直到a[j]>temp      
            a[j]=a[i];                    //a[i]后移到a[j]的位置    
        }  
        a[i]=temp;    
        if(k-1==i) return a[i];    
        else if(k-1<i)//左右区间递归查找      
            return Kminselect(a,s,i-1,k);  
        else return Kminselect(a,i+1,t,k); 
    }  
    else if(s==t && s==k-1) //区间内只有一个元素且为a[k-1]     
        return a[k-1];
}

int main(){    
    int A[N],i,k,n;  
    cin>>n;  
    for(i=0;i<n;i++)  
        cin>>A[i];  
    cin>>k;    
    cout<<Kminselect(A,0,n-1,k);   
    return 0;
}
找最大最小

算法步骤:输入(n个数的数组L);输出(max,min)

1.将n个元素两两一组分成 ⌈ n / 2 ⌉ \lceil n/2 \rceil ⌈n/2⌉ 组

2.每组比较,得到 ⌈ n / 2 ⌉ \lceil n/2 \rceil ⌈n/2⌉个较小和 ⌈ n / 2 ⌉ \lceil n/2 \rceil ⌈n/2⌉个较大

3.在 ⌈ n / 2 ⌉ \lceil n/2 \rceil ⌈n/2⌉个(n为奇数,是 ⌈ n / 2 ⌉ + 1 \lceil n/2 \rceil+1 ⌈n/2⌉+1)较小中找最小min

4.在 ⌈ n / 2 ⌉ \lceil n/2 \rceil ⌈n/2⌉ 个(n为奇数,是 ⌈ n / 2 ⌉ + 1 \lceil n/2 \rceil+1 ⌈n/2⌉+1)较大中找最大max

算法分析:行2 比较 ⌈ n / 2 ⌉ \lceil n/2 \rceil ⌈n/2⌉次,行3–4 比较至多 2 ⌈ n / 2 ⌉ − 2 2 \lceil n/2 \rceil -2 2⌈n/2⌉−2次

W(n) = ⌈ n / 2 ⌉ + 2 ⌈ n / 2 ⌉ − 2 = n + ⌈ n / 2 ⌉ − 2 = ⌈ 3 n / 2 ⌉ − 2 \lceil n/2 \rceil +2 \lceil n/2 \rceil -2 = n+\lceil n/2 \rceil -2 = \lceil 3n/2 \rceil -2 ⌈n/2⌉+2⌈n/2⌉−2=n+⌈n/2⌉−2=⌈3n/2⌉−2

#include <iostream>
#include<algorithm>
#define N 1000
using namespace std;
void maxmin(int A[],int &e_max,int &e_min,int low,int high){ 
    int mid,x1,x2,y1,y2;  
    int *a,*b,*c,*d;  
    a=&x1;b=&x2;c=&y1;d=&y2;  
    if((high-low<=1))  
    {      
        if(A[high]>A[low])   
        {     
            e_max=A[high];    
            e_min=A[low];    
        }     
        else  
        {        
            e_max=A[low];        
            e_min=A[high];    
        }  
    }  
    else 
    {     
        mid=(low+high)/2;    
        maxmin(A,*a,*c,low,mid);   
        maxmin(A,*b,*d,mid+1,high);   
        e_max=(*a)>(*b)?(*a):(*b);    
        e_min=(*c)<(*d)?(*c):(*d);  
    }
}

int main(){  
    int A[N],i,e_max,e_min,n;   
    cin>>n;  
    for(i=0;i<n;i++)   
        cin>>A[i];    
    maxmin(A,e_max,e_min,0,n-1);  
    cout<<e_max<<" "<<e_min;  
    return 0; 
} 

插值查找

​ 不同于折半查找总是把查找键和给定有序数组的中间元素进行比较,插值查找为了找到用来和查找键进行比较的数组元素,考虑了查找键的值。

二叉树的查找和插入

算法思想:二叉排序树的所有左子树的元素都小于子树根节点的元素,所有右子树的元素都大于子树根节点的元素。若要在这样一棵树中查找到一个给定值为k的元素时,递归以下步骤:

  • ① 若这棵树为空,则查找以失败结束。

  • ② 若这棵树不空,将k和该树的根T®进行比较。

    • 如果k==T®,查找成功,算法结束;
    • 如果它们不相等:
      • 当k<T®时,在左子树中继续查找;
      • 当k>T®时,在右子树中继续查找。

算法的每次迭代,查找一棵二叉排序树的问题简化为查找一个更小的二叉查找树

// 递归查找算法如下
BSTNode* searchBST(BSTree &BT,int k){
    // 输入:二叉排序树 BT,待查找值value
    // 输出:若查找成功,返回对应的指针;否则返回NULL 
    if(BT==NULL)    
        return  NULL;  
    if(BT->data == k)     
        return BT;  
    if(BT->data > k)   
        return searchBST(BT->lchild,k);  
    if(BT->data < k)    
        return searchBST(BT->rchild,k);
}

算法分析:平均查找长度为 A S L = 1 n ∑ i = 1 m n i c i ASL = {1 \over n} \sum_{i=1}^{m}n_i c_i ASL=n1​∑i=1m​ni​ci​,其中 n i n_i ni​是二叉排序树每层的结点数, c i c_i ci​是结点所在层数,m为树的深度;最好情况下为满二叉树, A S L 满 = 1 n ∑ i = 1 k 2 i − 1 × i ≈ log ⁡ 2 ( n + 1 ) ASL_满 = {1 \over n}\sum_{i=1}^k 2^{i-1}\times{i}≈\log_2{(n+1)} ASL满​=n1​∑i=1k​2i−1×i≈log2​(n+1);最差情况下为斜二叉树, A S L 斜 = 1 n ∑ i = 1 n i = ( n + 1 ) / 2 ASL_斜 = {1 \over n}\sum_{i=1}^n i = (n+1)/2 ASL斜​=n1​∑i=1n​i=(n+1)/2。

Chapter 5 分治法

​ 将一个难以直接解决的大问题,划分成一些规模较小的子问题,以便各个击破,分而治之。更一般地说,**将要求解的原问题划分成k个较小规模的子问题,对这k个子问题分别求解。**如果子问题的规模仍然不够小,则再将每个子问题划分为k个规模更小的子问题,如此分解下去,直到问题规模足够小,很容易求出其解为止,再将子问题的解合并为一个更大规模的问题的解,自底向上逐步求出原问题的解。

分治法的求解过程:

①**划分:**把规模为n的原问题划分为k个规模较小的子问题,并尽量使这k个子问题的规模大致相同。

②**求解子问题:**各子问题的解法与原问题的解法通常是相同的,可以用递归(或者循环)的方法求解各个子问题。

③**合并:**把各个子问题的解合并起来,合并的代价因情况不同有很大差异,分治算法的有效性很大程度上依赖于合并的实现。

注意:不是所有的分治法都比简单的蛮力法更有效。

1.快速排序

算法思想:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

算法描述:快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。

  • 从数列中挑出一个元素,称为 “基准”(pivot);
  • 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
  • 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

int Partition(SqList *L,int low,int high){
//快速排序的划分算法
	ElemType pivot=L->data[low];//将表中第一个元素设为枢纽值 划分表
	while(low<high){
		while(low<high&&L->data[high]>=pivot) --high;
		L->data[low]=L->data[high];  //将比枢纽值小的元素移动到左端
		while(low<high&&L->data[low]<=pivot) ++low;
		L->data[high]=L->data[low];  //将比枢纽值大的元素移动到右端
	}
	L->data[low]=pivot;  //枢纽元素存放到最终位置
		return low;
}
void QuickSort(SqList *L,int low,int high){
//快速排序 时间复杂度为O(nlogn){最坏为O(n^2)}  空间复杂度为O(logn){递归栈深度}
	int pivotpos;
	if(low<high){
		pivotpos=Partition(L,low,high);
		QuickSort(L,low,pivotpos-1);  //对两个子表进行递归排序
		QuickSort(L,pivotpos+1,high);
	}
}

算法分析:最好情况是数组乱序,且所有的分裂点位于相应子数组的中点;最差情况所有分裂点趋于极端,如升序数组的情况。
当 n > 1 时, C b e s t ( n ) = 2 C b e s t ( n / 2 ) + n C b e s t ( 1 ) = 0 根据主定理: C b e s t ( n ) = Θ ( n log ⁡ n ) 因为 a = b d 其中 a = 2 n = 2 k , b = 2 f ( n ) ∈ Θ ( n d ) ↔ n ,因此 d = 1 C w o r s t ( n ) = ( n + 1 ) + n + ⋯ + 3 ∈ Θ ( n 2 ) C a v g ( n ) ≈ 2 n ln ⁡ n ≈ 1.39 n log ⁡ 2 n 当n>1时,C_{best}(n) = 2C_{best}(n/2)+n \qquad C_{best}(1)=0 \\ 根据主定理:C_{best}(n) = \Theta(n\log{n}) \qquad 因为a=b^d \\ 其中a=2 \quad n=2^k,b=2 \quad f(n)\in \Theta(n^d) \leftrightarrow n,因此d=1 \\ C_{worst}(n) = (n+1) + n + \dots + 3 \in \Theta(n^2) \\ C_{avg}(n) ≈ 2n\ln{n} ≈ 1.39n\log_2{n} 当n>1时,Cbest​(n)=2Cbest​(n/2)+nCbest​(1)=0根据主定理:Cbest​(n)=Θ(nlogn)因为a=bd其中a=2n=2k,b=2f(n)∈Θ(nd)↔n,因此d=1Cworst​(n)=(n+1)+n+⋯+3∈Θ(n2)Cavg​(n)≈2nlnn≈1.39nlog2​n

2.归并排序

算法思想: 先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。

  • 把长度为n的输入序列分成两个长度为n/2的子序列;
  • 对这两个子序列分别采用归并排序;
  • 将两个排序好的子序列合并成一个最终的排序序列。

算法分析:归并排序是一种稳定的排序方法。和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是 O ( n l o g n ) O(nlogn) O(nlogn)的时间复杂度。代价是需要额外的内存空间。
当 n > 1 时, C w o r s t ( n ) = 2 C w o r s t ( n / 2 ) + n − 1 其中 C m e r g e = n − 1 根据主定理: C w o r s t ( n ) = Θ ( n log ⁡ n ) 因为 a = b d 其中 a = 2 n = 2 k , b = 2 f ( n ) ∈ Θ ( n d ) ↔ C m e r g e = n − 1 ,因此 d = 1 当n>1时,C_{worst}(n) = 2C_{worst}(n/2)+n-1 \qquad 其中C_{merge}=n-1 \\ 根据主定理:C_{worst}(n) = \Theta(n\log{n}) \qquad 因为a=b^d \\ 其中a=2 \quad n=2^k,b=2 \quad f(n)\in \Theta(n^d) \leftrightarrow C_{merge}=n-1,因此d=1 当n>1时,Cworst​(n)=2Cworst​(n/2)+n−1其中Cmerge​=n−1根据主定理:Cworst​(n)=Θ(nlogn)因为a=bd其中a=2n=2k,b=2f(n)∈Θ(nd)↔Cmerge​=n−1,因此d=1

每次合并操作的平均时间复杂度为 O ( n ) O(n) O(n),而完全二叉树的深度为 ⌈ l o g 2 n ⌉ \lceil log2n \rceil ⌈log2n⌉。归并排序的最好,最坏,平均时间复杂度均为 O ( n l o g n ) O(nlogn) O(nlogn)。

public class MergeSort {
	public static void main(String[] args) {
		int[] arrays = new int[] { 11, 3, 29, 49, 30, 7, 50, 63, 46 };
		System.out.println("未排序的数组:" + Arrays.toString(arrays));
		mergesort(arrays);
		System.out.println("排序后的数组:" + Arrays.toString(arrays));
	}
 
	public static void mergesort(int[] arr) {
         // 在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间
		int[] temp = new int[arr.length];
		sort(arr, 0, arr.length - 1, temp);
	}
 
	private static void sort(int[] arr, int left, int right, int[] temp) {
		if (left < right) {
			int mid = (left + right) / 2;
			sort(arr, left, mid, temp);// 左边归并排序,使得左子序列有序
			sort(arr, mid + 1, right, temp);// 右边归并排序,使得右子序列有序
			merge(arr, left, mid, right, temp);// 将两个有序子数组合并操作
		}
	}
 
	private static void merge(int[] arr, int left, int mid, int right, int[] temp) {
		int i = left;// 左序列指针
		int j = mid + 1;// 右序列指针
		int t = 0;// 临时数组指针
		while (i <= mid && j <= right) {
			if (arr[i] <= arr[j]) {
				temp[t++] = arr[i++];
			} else {
				temp[t++] = arr[j++];
			}
		}
		while (i <= mid) {// 将左边剩余元素填充进temp中
			temp[t++] = arr[i++];
		}
		while (j <= right) {// 将右序列剩余元素填充进temp中
			temp[t++] = arr[j++];
		}
		t = 0;
		// 将temp中的元素全部拷贝到原数组中
		while (left <= right) {
			arr[left++] = temp[t++];
		}
	}
}

3.分治法改进策略

减少子问题个数

分治算法的时间复杂度 W ( n ) = a W ( n ⁄ b ) + f ( n ) W(n)=aW(n⁄b)+f(n) W(n)=aW(n⁄b)+f(n),a:子问题数,n⁄b:子问题规模,f(n):划分与综合工作量。

当a较大,b较小,d(n)不大时,方程的解: W ( n ) = θ ( n log ⁡ b a ) W(n)=θ(n^{\log b^a}) W(n)=θ(nlogba),因此减少a是降低函数W(n)的阶的途径。

增加预处理:可减少 f ( n ) f(n) f(n),如处理最近对问题中,对平面中的点按 x x x轴 y y y轴坐标升序排列。

不应使用分治算法的情况

  • 一个规模为n的实例被划分成两个或多个实例,而每个实例的规模仍然几乎为n;
  • 一个规模为n的实例被划分为差不多n个规模为 n / c n/c n/c的实例,其中c为常量。(fabonacci)

4.平面点集的凸包问题

问题描述:给定大量离散点的集合Q,求一个最小的凸多边形,使得Q中的点在该多边形内或者边上。

应用背景: 图形处理中用于形状识别,如字形识别、碰撞检测等。

quickhull_1
quickhull_2

P.S 1

问题描述:设A和B都是从小到大排好序的的n个不等的数构成的数组,如果把A与B合并后的数组记住C,设计一个算法找出C的中位数。

P.S 2

问题描述:有n个人,其中有些人是诚实的,其他人可能会说谎。现在需要进行一项调查,该调查由一系列测试构成。每次测试如下进行:选两个人,然后提问:对方是否诚实?每个人的回答只能是“是”或者“否”。假定在这些人中,所有诚实的人回答都是正确的,而其他人的回答不能肯定是否正确。如果诚实的人数 > n / 2 >n/2 >n/2,试设计一个调查算法,以最小的测试数从其中找出一个诚实的人。

P.S 3

问题描述:考虑1,2,…,n的排列i1,i2,…in,如果其中存在ij,ik,使得j<k但是ij>ik,那么就称(ij,ik)是这个排列的一个逆序。一个排列含有逆序的个数称为这个排列的逆序数。例如排列2 6 3 4 5 1 含有8个逆序(2,1),(6,3),(6,4),(6,5),(6,1),(3,1),(4,1),(5,1),它的逆序数就是8.显然由1,2,…,n构成的所有排列n!个排列中,最小的逆序数是0,对应的排列就是1 2… n;最大的逆序数是n(n-1)/2,对应的排列就是n (n-1) …2 1逆序数越大的排列与原始序列的差异度就越大。

jpeg2000算法详解(1) JPEG2000标准主要由12部分组成,PART 1为其核心部分,其它每部分在该核心部分的基础上增加一些额外的特性和功能。每部分的内容和特征如下所示: PART 1:JPEG2000核心编码系统,标准号为ISO/IEC 15444-1,其规定了JPEG2000的基本特征和码流语法; PART 2:扩展系统,在核心系统上增加了许多特征; PART 3 : Motion JPEG2000 ( MJP... 阅读详情

相关推荐

【数据结构与算法学习笔记】PART1:算法分析(计算,计算模型,大O记号,算法分析,迭代与递归,动态控制)

在本科的时候就学过数据结构,那时候用的是清华大学C++版本的教材。上研究生之后,在实际学习与工程中愈发发觉数据结构的重要性,所以重新进行了数据结构的再学习,采用了清华大学邓俊辉副教授的视频课程,同时在学习中整理笔记如下。 两个重要方面: 正确性:算法功能与问题要求一致?数学证明?   成本:运行时间,所需存储空间     如何度量,如何比较? 进行归纳概括:划分等价类  观察:问题实

小伙儿在这儿烤地瓜 1280

排序算法的稳定性和在位性

冒泡排序 稳定 在位 选择排序 不稳定 在位 插入排序 稳定 在位 基数排序 不稳定 不在位 shell排序 不稳定 在位 快速排序 不稳定 在位 合并排序 稳定 不在位 推排序 不稳定 在位 分布计数排序 稳定 不在位 ...

m0_59069522的博客 1573

算法入门——part1.基础知识(递归式、概率)

算法入门——part1.基础知识(下)1.递归式2.概率

nanashi_F的博客 624

算法之位运算

目录: 算法之位运算 位运算种类 具体分析 and运算 &amp;amp;amp;amp;amp;amp; or运算 | xor运算 ^ 算法之位运算 位运算种类 含义 Pascal语言 C语言 C#语言 Java 按位与 a and b a &amp;amp;amp;amp;amp;amp; b a &amp;amp;amp;amp;amp;amp; b a &amp;amp;amp;a

随芯的博客 515

在位个数算法和目标距离算法解决经典的八数码问题

用不在位个数算法和目标距离算法解决经典的八数码问题

Xilinx XDMA IP核配置实战:AXI Memory Mapped vs Stream选型指南(附PCIe调试技巧)

本文深入解析Xilinx XDMA IP核配置中AXI Memory Mapped与AXI Stream两种DMA接口的核心区别与选型策略。针对PCIe高速数据传输场景,详细对比了二者在数据特性、延迟、硬件资源及软件复杂度上的差异,为FPGA工程师在数据采集、实时处理等项目中做出高效设计决策提供实战指南,并附有关键的PCIe调试技巧。

qsc901234的博客 585

位运算在算法中的应用

什么是位运算及位运算在算法中的应用

weixin_73044854的博客 384

Qt中常见的面试题总结

基于Qt开发的一些常用面试题

一名热爱编程的程序员 8493

基本概念

排序算法有两个特性:稳定性和在位性 稳定性:如果一个算法保留了等值元素再输入中的相对顺序,就可以说它是稳定的(stable)。 在位性:如果一个算法不需要额外的存储空间,就可以说它是在位的(in-place)。 转载于:https://www.cnblogs.com/kongieg/p/11367112.html...

weixin_30847865的博客 334

Android开发者必看:5分钟搞定DeepSeek API接入(附完整代码示例)

本文为Android开发者提供快速接入DeepSeek API的完整指南,包含环境配置、网络层设计、请求体构建等关键步骤,并附有详细代码示例。通过工程化实践,帮助开发者高效集成AI对话、代码生成等高级功能,提升移动应用竞争力。

bush的博客 538

知识图谱-KGE-语义匹配-双线性模型-2018:SimplE

【paper】 SimplE Embedding for Link Prediction in Knowledge Graphs 【简介】 本文是加拿大英属哥伦比亚大学的两位学者发表在 NIPS 2018 上的工作,文章提出了 SimplE(Simple Embedding)。这篇和前面一篇差不多,也是对 1927 年的 CP 进行改进(简化),传统CP为实体分配的两个向量是独立训练的,SimplE利用关系的逆在三元组打分函数中加上了一个对称项,使得每个实体的两个向量依赖学习,并且 SimplE 的复杂度随

u013250861的博客 958

【算法设计与分析】——算法及效率分析基础(二)

算法分析框架 基本操作:它通常是算法最内层循环中最费时的操作。四则运算中:加、减、乘、除。其中最耗时的操作是除法,其次是乘法,最后是加法和减法。 问题规模:排序:数组中元素个数n. 检索:被检索数组的元素个数n. 矩阵相乘:矩阵的行列数i,j,k. 图的遍历:图的顶点数n,...

qq_40306987的博客 2203

cdk&java | 绘制含有丰富信息的化学反应(适合论文、网页、PPT)

package org.example; import org.openscience.cdk.DefaultChemObjectBuilder; import org.openscience.cdk.depict.DepictionGenerator; import org.openscience.cdk.exception.CDKException; import org.openscience.cdk.interfaces.IReaction; import org.openscience.cdk.

ASKCOS博客 899

算法设计与分析基础(第三版)Part 1 绪论

Part 1 绪论1.1什么是算法1.2算法问题求解基础1.3重要的问题类型排序字符串处理图问题组合问题几何问题数值问题1.4基础数据结构线性数据结构图树集合与字典 1.1什么是算法 算法是一系列解决问题的明确指令 简单来说,算法——解决问题——代码 1.2算法问题求解基础 理解问题——了解计算设备的性能——在精确解法和近似解法之间做出选择——算法的设计技术——确定适当的数据结构——算法描述——算法的正确性证明——算法的分析——为算法写代码 1.3重要的问题类型 排序 字符串处理 图问题 组合问题 几何问题

暖阳Shine 639

30天入门 SAP BTP 开发系列之八:动手在 SAP BTP 上开发第一个 ABAP 透明表

ABAP Cloud 环境下创建透明表的方法与数据迁移实践 本文介绍了在 SAP BTP ABAP 环境中创建透明表并迁移数据的完整流程。与传统的 SE11 事务码不同,ABAP Cloud 需通过 ABAP Development Tool 创建数据库表,使用代码定义表结构。作者详细演示了如何: 在开发包中创建名为 Z优快云_TRAVEL 的透明表 使用 ABAP 代码定义表字段和约束 通过 ABAP 类实现数据从标准表/DMO/TRAVEL 到新建表的迁移 验证数据迁移结果 文中还提供了相关技术概念的

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 1788

手把手教你学Simulink——基于经典PID控制的电机系统场景实例:BLDC电机PID电流控制策略仿真

*无刷直流电机(Brushless DC Motor, BLDC)**因其高效率、长寿命和低维护成本,在众多领域如家用电器、工业自动化、电动汽车等得到了广泛应用。为了实现对BLDC电机的有效控制,通常采用基于霍尔传感器或反电动势检测的换相技术,并结合电流闭环控制以提高系统的动态响应和稳定性。本教程将带你使用构建一个基于PID控制的BLDC电机电流控制策略仿真模型BLDC电机建模PID控制器设计与应用系统仿真与调试控制效果分析本文介绍了如何使用构建一个。

MHD0815的博客 197

part1 算法分析与程序设计基础

1.算法特征: 输入性(0个或多个输入) 输出性(1个或多个输出) 确定性(任何条件下,算法只有唯一执行路径) 有穷性 可行性   2.判断一个算法好坏的标准: 正确性、可读性、健壮性、效率   3、算法描述:自然语言、框图、伪代码、程序设计语言(程序=数据结构+算法)。   4.C++程序文件分为4个部分:预处理指令、全局说明、程序主函数、用户自定义函数。   cer

王小东大将军的博客 731

SAP UI5 控件剖析系列之 sap.m.DatePicker:单日选择的工程化用法、最佳实践与常见坑

在 SAP UI5 的输入控件族里,是最常被使用的日期选择控件。它把一个可编辑的输入框与一个弹出的日历整合在一起,允许用户用触摸、鼠标或键盘在本地化格式下选择某一天;控件既可以从弹出的日历里点选,也可以直接在输入框里键入日期。官方 Fiori 设计规范对它的定位就是:完成单个日期的选择,不牵涉时间,也不负责跨天的范围选择。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 343

《算法设计与分析基础》【part2】

《算法设计与分析基础》读书笔记,后5章内容

Morejay的博客 1173

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

算法设计与分析_Part1

给定含有S个元素的多重集合S(元素未排序),每个元素在S中出现的次数称为该元素的重数。例如,S={2,1,2,3,2,5}。对于给定的由n个自然数组成的多重集S,计算S的众数及其重数。,n,奇数号圆盘着蓝色,偶数号圆盘着红色,如图所示。规则(4):在满足移动规则(1)-(3)的前提下,可将圆盘移至 A,B,C 中任一塔座上。给定一个自然数n,由n开始可以依次产生半数集set(n)中的数如下:;对于给定的自然数n,计算半数集set(n)中的元素个数。只有1行,给出半数集set(n)中的元素个数。

TengMMVP的博客 863

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

算法设计与分析基础(第三版)Part 3 蛮力法

蛮力法3.1选择排序和冒泡排序3.1.1选择排序3.1.2冒泡排序3.2顺序查找和蛮力字符串匹配3.2.1顺序查找3.2.2蛮力字符串匹配3.3最近对和凸包问题蛮力算法3.3.1最近对问题凸包问题3.4穷举查找3.4.1 旅行商问题3.4.2 背包问题3.4.3分配问题3.5深度优先查找和广度优先查找3.5.1 深度优先查找3.5.2 广度优先查找 蛮力法——简单直接地解决问题的方法 3.1选择排序和冒泡排序 3.1.1选择排序 选择排序开始的时候,扫描整个列表,找到它的最小元素,然后和第一个元素交换,将

暖阳Shine 1064

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

算法设计与分析课程Part1笔记(1)

所有笔记PDF版本,外加一些习题和基本答案,已经上传至http://ishare.iask.sina.com.cn/f/25611722.html ----------------------------------------------------------------------------------------- 1. Introduction 算法作为计算机科学的一个重要分支,

moodytong的专栏 1317

算法设计与分析基础(第三版)Part 8 动态规划

动态规划8.1 三个基本例子例一币值最大化问题例二找零问题例三硬币收集问题8.2 背包问题和记忆功能背包问题记忆功能8.3 最优二叉查找树8.4 Warshall算法和Floyd算法Warshall算法(计算有向图传递闭包)Floyd算法(计算全部最短路径) 如果问题是由交叠的子问题构成的,我们就可以用动态规划技术来解决它。一般来说,这样的子问题出现在对给定问题求解的地推关系中,这个递推关系中包含了相同类型的更小子问题的解。动态规划法建议,与其对交叠的子问题一次又一次地求解,不如对每个较小的子问题只求解一

暖阳Shine 829

算法分析 - Algorithms, Part I, week 1 ANALYSIS OF ALGORITHMS

前言 上一篇:并查集下一篇:栈和队列 在算法性能上我们常常面临的挑战是我们的程序能否求解实际中的大型输入:--为什么程序运行的慢?--为什么程序耗尽了内存? 没有理解算法的性能特征会导致客户端的性能很差,为了避免这种情况的出线,需要具备算法分析的一些知识。此篇主要涉及一些基础数学知识和科学方法,以及如何在实践应用中使用这些方法理解算法的性...

weixin_33890499的博客 778

Hunyuan3D-Part:腾讯混元3D部件生成与分割的突破性架构

腾讯混元Hunyuan3D-Part项目代表了3D人工智能领域的重要突破,专注于三维物体的部件级生成与分割技术。该项目通过创新的P3-SAM和X-Part两大核心组件,实现了从整体网格到精细化部件分解的全流程智能化处理,为3D内容创作、工业设计和数字孪生等领域提供了强大的技术支撑。 ## 项目亮点速览:三大核心技术突破 **原生3D部件分割技术**:P3-SAM模块实现了无需2D投影的纯3D部

gitblog_00707的博客 913

表观遗传学数据分析学习笔记-Part1

BWA(Burrows-Wheeler Aligner)是一套广泛用于基因组比对的开源工具,包含三个主要算法:BWA-backtrack、BWA-SW和BWA-MEM。BWA MEM是其中最新且最常用的算法,专为长读段(如Illumina HiSeq 2500及更高版本的数据)设计。BWA-backtrack适用于短读段(长度≤100bp),基于原始的BWT(Burrows-Wheeler Transform)算法。BWA-SW支持更长的读段(如454或Sanger数据),但效率较低。

weixin_50395815的博客 2203
上一篇: DL for Scratch 读书笔记
下一篇: 【学习笔记】尚硅谷-AJAX
Morejay
博客等级 码龄9年 103粉丝 24原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值