经典算法
动态规划-航线设置
问题描述:美丽的莱茵河畔,每边都分布着N个城市,两边的城市都是唯一对应的友好城市,现需要在友好城市开通航线以加强往来.但因为莱茵河常年大雾,如果开设的航线发生交叉现象就有可能出现碰船的现象.现在要求近可能多地开通航线并且使航线不能相交!
假如你是一个才华横溢的设计师,该如何设置友好城市间的航线使的航线数又最大又不相交呢?
分析:此问题可以演化成求最大不下降序列来完成.源程序如下:
program dongtai; {动态规划之友好城市航线设置问题}
var
d:array[1..1000,1..4] of integer;
i,j,k,n,L,p:integer;
procedure print(L:integer); {打印结果}
begin
writeLn('最多可设置的航线数是 : ',k);
repeat
writeLn(d[L,1]:4,d[L,2]:4); {输出可以设置航线的友好城市代码}
L:=d[L,4]
untiL L=0
end;
begin
writeLn('输入友好城市对数: ');
readLn(n);
writeLn('输入友好城市对(友好城市放在同一行:'); {输入}
for i:=1 to n do
readLn(d[i,1],d[i,2]); {D[I,1]表示起点,D[I,2]表示终点}
for i:=1 to n do
begin
d[i,3]:=1; {D[I,3]表示可以设置的航线条数}
d[i,4]:=0 {D[I,4]表示后继,即下一条航线从哪里开始设置,为0表示不能设置下一条航线}
end;
for i:=n-1 downto 1 do {从倒数第二个城市开始规划}
begin
L:=0; p:=0; {L表示本城市后面可以设置的航线数,P表示下条航线从哪个城市开始}
for j:=i+1 to n do {找出本城市后面可以设置的最大航线数和小条航线到底从哪个城市开始设置}
if (d[i,2] L) then
{如果本城市I的终点小于后面城市的终点(即不相交)} {并且此城市后面可以设置的航线数大于L}
begin
L:=d[j,3]; {那么L等于城市J的可以设置航线数}
p:=j {P等于可以设置下条航线的城市代码}
end;
if L>0 then {如果本城市后面总共可以设置的航线数>0则}
begin
d[i,3]:=L+1; {本城市可以设置的航线数在下个城市可以设置航线数的基础上加1}
d[i,4]:=p {D[I,4]等于本城市后续城市的代码}
end
end;
k:=d[1,3]; {K为可以设置最大航线数,假设初值为第一个城市可以设置的航线数}
L:=1; {L为城市代码,初值为第一个城市}
for i:=2 to n do {找出可以设置航线的最大值,赋值给K,同时L记下哪个可以设置最大航线数的城市代码}
if d[i,3]>k then
begin
k:=d[i,3];
L:=i
end;
for i:=1 to n do {打印结果,因为有可能有多种方案,所以只要哪个城市可以设置的航线数等于最大值K就打印结果}
if d[i,3]=k then print(i)
end.
归并排序算法
合并排序(MERGE SORT)是又一类不同的排序方法,合并的含义就是将两个或两个以上的有序数据序列合并成一个新的有序数据序列,因此它又叫归并算法。它的基本思想就是假设数组A有N个元素,那么可以看成数组A是又N个有序的子序列组成,每个子序列的长度为1,然后再两两合并,得到了一个 N/2 个长度为2或1的有序子序列,再两两合并,如此重复,值得得到一个长度为N的有序数据序列为止,这种排序方法称为2—路合并排序。
例如数组A有7个数据,分别是: 49 38 65 97 76 13 27,那么采用归并排序算法的操作过程如图7所示:
初始值 [49] [38] [65] [97] [76] [13] [27]
看成由长度为1的7个子序列组成
第一次合并之后 [38 49] [65 97] [13 76] [27]
看成由长度为1或2的4个子序列组成
第二次合并之后 [38 49 65 97] [13 27 76]
看成由长度为4或3的2个子序列组成
第三次合并之后 [13 27 38 49 65 76 97]
图6 归并排序算法过程图
合并算法的核心操作就是将一维数组中前后相邻的两个两个有序序列合并成一个有序序列。合并算法也可以采用递归算法来实现,形式上较为简单,但实用性很差。
合并算法的合并次数是一个非常重要的量,根据计算当数组中有3到4个元素时,合并次数
是2次,当有5到8个元素时,合并次数是3次,当有9到16个元素时,合并次数是4次,按照这一
规律,当有N个子序列时可以推断出合并的次数是X(2 >=N,符合此条件的最小那个X)。
源程序:
program hebing;
const
n=10;
var
a:array[1..n] of integer;
i:integer;
procedure init;
var
i:integer;
begin
for i:=1 to n do read(a[i]);
readln;
end;
枚举法
有4个学生,上地理课时提出我国四大谈水湖的排列次序如下:
甲:洞庭湖最大,洪泽湖最小,鄱阳湖第三;
乙:洪泽湖最大,洞庭湖最小,鄱阳湖第二,太湖第三;
丙:洪泽湖最小,洞庭湖第三;
丁:鄱阳湖最大,太湖最小,洪泽湖第二,洞庭湖第三;
对于各湖泊应处的位置,每个人只说对了一个。根据以上描述和条件,编写程序,让计算机判断一下各湖泊应该处于第几位。
解题思路:设置洞庭湖、洪泽湖、鄱阳湖、太湖分别用字母A、B、C、D代表,最大到最小依次用数字4——1表示。应用枚举法可以解决此问题,不过稍微复杂罗嗦点。
源程序如下:
program hupo;
var
a,b,c,d:integer;
begin
for b:=1 to 4 do
for a:=1 to 4 do
if ((b=1) and (a<>2)) or ((a=2) and (b<>1)) then
if a<>b then
for c:=1 to 4 do
if (a<>c) and (b<>c) then
if (a+b+c<>7) and (a+b<>5) and (a+c<>6) and (b+c<>3) then
for d:=1 to 4 do
if c<>d then
if (b+a<>5) and (b+c<>7) and (b+d<>6) then
if (a+c<>4) and (a+d<>3) and (c+d<>5) then
if (c+d<>5) and (c+b<>7) and (c+a<>6) then
if (d+b<>4) and (d+a<>3) and (b+a<>5) then
writeln('a=',a,' b=',b,' c=',c,' d=',d)
end.
改进程序:
program ygzj;
var
a,b,c,d:integer;
begin
for a:=1 to 4 do
for b:=1 to 4 do
for c:=1 to 4 do
begin
d:=10-a-b-c;
if ord(a=1)+ord(b=4)+ord(c=3)=1 then
if ord(b=1)+ord(a=4)+ord(c=2)+ord(d=3)=1 then
if ord(b=4)+ord(a=3)=1 then
if ord(c=1)+ord(d=4)+ord(b=2)+ord(a=3)=1 then
if a*b*c*d=24 then
writeln('洞庭湖第':3,a:3,'洪泽湖第':3,b:3,'波阳湖第':3,c:3,'太湖第':3,d:3);
end;
writeln
end.
数字全排列问题
数字全排列问题:
任意给出从1到N的N个连续的自然数,求出这N个自然数的各种全排列。如N=3时,共有以下6种排列方式:
123,132,213,231,312,321。
注意:数字不能重复,N由键盘输入(N<=9)。
解题思路:
应用回溯法,每个数的取法都有N个方向(1——N),当取够N个数时,输出一个排列,然后退后一步,取前一个数的下一个方向(即前一个数+1),并且要保证所有数字不能重复。当前数字的所有方向都取完时,继续退一步,一直重复到第一个数为止。
程序代码:
program quanpailie; {数字全排列问题}
var
a:array[1..9] of integer;
k,x,n:integer;
function panduan(j,h:integer):boolean; {判断当前数字是否能赋值给当前数组元素}
var
m:integer;
begin
panduan:=true;
for m:=1 to h-1 do
if a[m]=j then panduan:=false {如果当前数字与前面的数组元素相同则不能赋值}
end;
procedure try(h:integer);
var
i,j,m:integer;
begin
for j:=1 to n do
if panduan(j,h) then
begin
a[h]:=j; {能够赋值,且长度k加一}
k:=k+1;
if k=n then {如果长度达到N则表示一种组合已经完成,输出结果}
begin
for m:=1 to n do
write(a[m]);
write('':4);
x:=x+1; {每输出一种排列方式加一}
if x mod 5=0 then writeln; {每行输出5种排列方案}
end
else
try(h+1); {对下一个数组元素进行赋值}
k:=k-1 {回溯的时候一定要把长度减一}
end
end;
begin
writeln('输入 N:');
readln(n);
k:=0; {k表示长度,长度初始值为0}
x:=0; {x表示总的排列方式}
try(1); {对第一个数组元素赋值}
writeln('共有 ', x ,' 种排列方案')
end.
优化高精度减法
阳明
{说明:用字符串来存放减数和被减数,最大限制255位减255位}
{优化在于用一个数组元素存放4位数,节省存储空间}
program jjzx; {本程序没有考虑两负数相间减}
var s,s1,s2:string;
a,b,c:array[1..260] of integer;
i,l,m,k1,k2:integer;
d:char; {D用来表示正负号}
begin
writeln('input s1:');readln(s1);
writeln('input s2:');readln(s2);
l:=length(s1);
m:=length(s2);
if l
begin
s:=s1;s1:=s2;s2:=s;d:='-'
end;
if l=m then {如果长度一样则直接比较,S1小就要与S2调换}
if s1
begin
s:=s1;s1:=s2;s2:=s;d:='-'
end;
l:=length(s1); m:=length(s2);
k1:=261;
repeat {转换S1,一个数组元素用来存放4位数字}
k1:=k1-1;
s:=copy(s1,l-3,4);
val(s,a[k1],i);
s1:=copy(s1,1,l-4);
l:=l-4
until l<=0;
k2:=261; {转换S2}
repeat
k2:=k2-1;
s:=copy(s2,m-3,4);
val(s,b[k2],i);
s2:=copy(s2,1,m-4);
m:=m-4
until m<=0;
for i:=260 downto k1 do
if a[i]
begin
c[i]:=a[i]+10000-b[i]; {注意是加上10000}
a[i-1]:=a[i-1]-1;
end
else
c[i]:=a[i]-b[i];
writeln('jie guo shi :');
write(d:2); {首先输出符号位}
write(c[k1]); {在输出最高位,因为最高位不用补0}
for i:=k1+1 to 260 do {注意补0的方法}
begin
if c[i]<1000 then write('0');
if c[i]<100 then write('0');
if c[i]<10 then write('0');
write(c[i]);
end;
writeln
end.
高精度阶乘
program jjzx; {本程序最大限制为求N(N<999)的阶乘}
var
a,b,c:array[1..1000] of integer;
i,j,l,m,k1,k2,x,y,z,w,n,t:integer;
procedure chengfa; {高精度乘法}
begin
for l:=1 to k1 do
for m:=1 to k2 do
begin
x:=a[l]*b[m];
y:=x div 10;
z:=x mod 10;
w:=l+m-1;
c[w]:=c[w]+z;
c[w+1]:=c[w+1]+y+c[w] div 10;
c[w]:=c[w]mod 10
end;
k1:=k1+k2; {位数为K1、K2的两数相乘最大只有K1+K2位}
if c[k1]=0 then k1:=k1-1; {如果最高位为0则位数减少一位}
for t:=1 to k1 do a[t]:=c[t]; {把一次高精度相乘的结果放到A数组中,以便下次相乘}
for t:=1 to k1 do c[t]:=0; {同时把数组C清空,以便下次相乘,因为每调用此过程一次都是一次 全新的高精度乘法,所以数组C必须清空}
end;
procedure zhuanhuan; {把I每一位分解开分别赋值给数组B的每一个元素}
begin
if i>=100 then
begin
k2:=3;
b[3]:=i div 100;
b[2]:=(i-b[3]*100) div 10;
b[1]:=i-b[3]*100-b[2]*10
end
else
if i>=10
then
begin
k2:=2;
b[2]:=i div 10;
b[1]:=i-b[2]*10
end
else
begin
k2:=1;
b[1]:=i
end;
end;
begin
writeln('input:');
readln(n);
a[1]:=1; {最后结果放在数组A中}
k1:=1;
for i:=2 to n do
begin
zhuanhuan;
chengfa;
end;
writeln(n:2,'!= ');
for i:=k1 downto 1 do write(a[i])
end.
高精度减法
{说明:用字符串来存放减数和被减数,最大限制255位减255位}
program jjzx; {本程序没有考虑两负数相减}
var s,s1,s2:string;
a,b,c:array[1..260] of integer;
i,l,m,k1,k2:integer;
d:char; {D用来表示正负号}
begin
writeln('input s1:');readln(s1);
writeln('input s2:');readln(s2);
l:=length(s1); m:=length(s2);
if l
begin
s:=s1; s1:=s2; s2:=s; d:='-'
end;
if l=m then {如果长度一样则直接比较,S1小就要与S2调换}
if s1
begin
s:=s1;s1:=s2;s2:=s;d:='-'
end;
l:=length(s1); m:=length(s2); {为什么要再次得到长度}
k1:=261; {为什么是261}
for i:=l downto 1 do {S1转换过程}
begin
k1:=k1-1;
a[k1]:=ord(s1[i])-48
end;
k2:=261;
for i:=m downto 1 do {S2转换过程}
begin
k2:=k2-1;
b[k2]:=ord(s2[i])-48
end;
for i:=260 downto k1 do {开始计算}
if a[i]
begin
c[i]:=a[i]+10-b[i];
a[i-1]:=a[i-1]-1 {为什么下标是I-1}
end
else
c[i]:=a[i]-b[i];
writeln('jie guo shi :');
write(d:2); {首先输出符号位}
for i:=k1 to 260 do write(c[i]);
writeln
end.
高精度乘法
{说明:用字符串来存放乘数,最大限制255位乘255位}
program jjzx; {本程序只考虑整数相乘}
var
s1,s2:string;
a,b:array[1..255] of integer;
c:array[1..510] of integer; {数组C用来存放结果,最大510位,为什么?}
i,j,l,m,k1,k2,x,y,z,w:integer;
begin
writeln('input s1:'); readln(s1);
writeln('input s2:'); readln(s2);
l:=length(s1); m:=length(s2); {得到两个字符串的长度}
k1:=0;
for i:=l downto 1 do {S1转换过程,把低位放在A[1]中}
begin
k1:=k1+1;
a[k1]:=ord(s1[i])-48;
end;
k2:=0;
for i:=m downto 1 do {S2转换过程,把低位放在B[1]中}
begin
k2:=k2+1;
b[k2]:=ord(s2[i])-48 ;
end;
for i:=1 to k1 do {开始计算,从低位开始乘}
for j:=1 to k2 do
begin
x:=a[i]*b[j];
y:=x div 10;
z:=x mod 10;
w:=i+j-1;
c[w]:=c[w]+z;
c[w+1]:=c[w+1]+y+c[w] div 10;
c[w]:=c[w]mod 10
end;
w:=k1+k2;
if c[w]=0 then w:=w-1; {判断最高位是否有数}
writeln('相乘结果是 :');
for i:=w downto 1 do write(c[i]);{输出的时候注意顺序}
writeln
end.
Dijkstra最短路径(一点到各顶点最短路径)
{本程序解决6个顶点之间的最短路径问题,各顶点间关系的数据文件在sj.txt中}
{如果顶点I到顶点J不能直达就设置距离为30000}
program dijkstra;
type
jihe=set of 0..5;
var
a:array[0..5,0..5] of integer;
dist:array[0..5] of integer;
i,j,k,m,n:integer;
fv:text;
s:jihe;
begin
s:=[0];
assign(fv,'sj.txt');
reset(fv);
for i:=0 to 5 do {从文件中读数据,其中a[i,j]代表从顶点i到顶点j的直达距离,如果不通用30000代替}
begin
for j:=0 to 5 do read(fv,a[i,j]);
readln(fv)
end;
for i:=1 to 5 do {设置DIST数组的初始值,即为顶点0到各顶点的直达距离(算法的第一步)}
dist[i]:=a[0,i];
for i:=1 to 5 do
begin
m:=0;
dist[m]:=30000; {设置DIST[M]的目的是为下面的一步做准备,即在DIST数组中一个最小的值}
for j:=1 to 5 do {算法的第二步,找最小的DIST值}
if (not (j in s)) and (dist[m]>dist[j])
then m:=j ; {用M来记录到底是哪个顶点}
s:=s+[m]; {把顶点加入S中}
for k:=1 to 5 do {算法的第三步,修改后面的DIST值}
if (not (k in s)) and (dist[k]>dist[m]+a[m,k])
then
dist[k]:=dist[m]+a[m,k]
end;
writeln('原各顶点间的路径关系是:(30000代表不通)');
for i:=0 to 5 do
begin
for j:=0 to 5 do write(a[i,j]:6);
writeln
end;
writeln; writeln;
八皇后问题
{问题描述:在国际象棋8X8的棋盘里摆放8个皇后,使每个皇后都能生存而不互相冲突,即同一行、同一列同对角线(包括主对角线和辅对角线)都只能有一个皇后}
program eightqueen; {本程序可以搜索出所有的解}
var
a,b:array[1..8] of integer;
c:array[-7..7] of integer;
d:array[2..16] of integer;
i,k:integer; {K变量用来存放答案的个数}
fv:text;
procedure print;
var
i:integer;
begin
for i:=1 to 8 do
writeln(fv,'第',i:2, '行放在第', a[i]:2,'列'); {结果输出到文件里}
k:=k+1; {每输出一个答案计数加1}
writeln(fv)
end;
procedure try(i:integer);
var
j:integer;
begin
for j:=1 to 8 do
if (b[j]=0) and (c[i-j]=0) and (d[i+j]=0) then
begin
a[i]:=j;
b[j]:=1; {宣布占领列、主副对角线}
c[i-j]:=1;
d[i+j]:=1;
if i<8 then try(i+1) else print;
b[j]:=0; {释放占领列、主副对角线}
c[i-j]:=0;
d[i+j]:=0
end
end;
begin
for i:=1 to 8 do a[i]:=0;
for i:=-7 to 7 do c[i]:=0;
for i:=2 to 16 do d[i]:=0;
k:=0;
assign(fv,'jieguo.txt'); {指定文件与文件变量相联系}
rewrite(fv); {以写的方式打开文件}
try(1);
close(fv); {一定要记得关闭文件,不然数据有可能丢失}
writeln('共有 ',k:3,' 种摆法')
end.
八皇后问题
{问题描述:在国际象棋8X8的棋盘里摆放8个皇后,使每个皇后都能生存而不互相冲突,即同一行、同一列同对角线(包括主对角线和辅对角线)都只能有一个皇后}
program eightqueen; {本程序可以搜索出所有的解}
var
a,b:array[1..8] of integer;
c:array[-7..7] of integer;
d:array[2..16] of integer;
i,k:integer; {K变量用来存放答案的个数}
fv:text;
procedure print;
var
i:integer;
begin
for i:=1 to 8 do
writeln(fv,'第',i:2, '行放在第', a[i]:2,'列'); {结果输出到文件里}
k:=k+1; {每输出一个答案计数加1}
writeln(fv)
end;
procedure try(i:integer);
var
j:integer;
begin
for j:=1 to 8 do
if (b[j]=0) and (c[i-j]=0) and (d[i+j]=0) then
begin
a[i]:=j;
b[j]:=1; {宣布占领列、主副对角线}
c[i-j]:=1;
d[i+j]:=1;
if i<8 then try(i+1) else print;
b[j]:=0; {释放占领列、主副对角线}
c[i-j]:=0;
d[i+j]:=0
end
end;
begin
for i:=1 to 8 do a[i]:=0;
for i:=-7 to 7 do c[i]:=0;
for i:=2 to 16 do d[i]:=0;
k:=0;
assign(fv,'jieguo.txt'); {指定文件与文件变量相联系}
rewrite(fv); {以写的方式打开文件}
try(1);
close(fv); {一定要记得关闭文件,不然数据有可能丢失}
writeln('共有 ',k:3,' 种摆法')
end.
地图四色问题
{问题描述:任何一张地图只要用四种颜色进行填涂,就可以保证相邻省份不同色}
program tt;
const num=20;
var a:array [1..num,1..num] of 0..1;
s:array [1..num] of 0..4; {用1-4分别代表RBWY四种颜色;0代表末填进任何颜色}
k1,k2,n:integer;
function pd(i,j:integer):boolean;{判断可行性:第I个省填上第J种颜色}
var k:integer;
begin
for k:=1 to i-1 do {一直从第一个省开始进行比较一直到I省减一的那个省,目的是对已经着色的省份来进行比较,因为>I的省还没 有着色,比较没有意义,着色的顺序是先第一、二、三……I个省}
if (a[i,k]=1) and (j=s[k]) then {省I和省J相邻且将填进的颜色和已有的颜色相同}
begin
pd:=false; {即不能进行着色}
exit; {退出当前函数}
end;
pd:=true; {可以进行着色}
end;
procedure print;{打印结果}
var k:integer;
begin
for k:=1 to n do{将数字转为RBWY串}
case s[k] of
1:write('R':4);
2:write('B':4);
3:write('W':4);
4:write('Y':4);
end;
writeln;
end;
procedure try(i:integer);
var j:integer;
begin
for j:=1 to 4 do
if pd(i,j) then begin
s[i]:=j;
if i=n then print
else try(i+1); {对下一个省进行着色}
s[i]:=0; {不能进行着色,将当前状态设置0,即不进行着色}
end;
end;
BEGIN
write('please input city number: '); readln(n);
writeln('please input the relation of the cities:');
for k1:=1 to n do
begin
for k2:=1 to n do read(a[k1,k2]); {A[K1,K2]=1表示省K1、K2相邻,为0就不相邻}
readln;
end;
for k1:=1 to n do s[k1]:=0; {把所有的颜色设置为0,即还没有进行着色}
try(1);
END.
穿越迷宫
{本程序假设迷宫是一个4 X 4的矩阵,入口在A[1,1],出口在A[4,4]}
{矩阵数据放在文件shuju3.txt 中}
program mikong;
var
a,b,c:array[1..4,1..4] of integer; {数组A用来存放迷宫路径,约定元素值为0表示通,1表示不通}
{数组B用来存放方向增量}
{数组C用来记录结果,当第I步移到某一元素时,该元素就等于I}
i,j,k,m,n:integer;
fv:text;
q:boolean; {用来标记迷宫是否有出路}
procedure print;
var
m,n:integer;
begin
q:=true; {如果打印步骤,表示肯定有出路}
writeln;
writeln;
writeln('穿越迷宫的步骤是:');
for m:=1 to 4 do
begin
for n:=1 to 4 do
write(c[m,n]:4);
writeln;
end
end;
procedure try(x,y,i:integer);
var
u,v,k:integer;
begin
for k:=1 to 4 do {可以有4个方向选择}
begin
u:=x+b[k,1]; {当前坐标加上方向增量}
v:=y+b[k,2];
if (u>=1) and (u<=4) and (v>=1) and (v<=4) then {判断是否越界}
if (a[u,v]=0) and (c[u,v]=0) then {判断是否为0,为0就表示通,为1就表示不通}
begin
if (x=2) and (y=3) then writeln('aaaaaaaaaaaa');
c[u,v]:=i; {数组 C打上记号,表示此元素是第I步到达}
if (u<>4) or (v<>4) then {判断是否到出口}
try(u,v,i+1) {没有就继续走}
else print;
c[u,v]:=0 {下一路所有方向都不通,清除本次所做的标记}
end
end
end;
begin
assign(fv,'shuju3.txt');
reset(fv);
for i:=1 to 4 do
begin
for j:=1 to 4 do
read(fv,a[i,j]);
readln(fv)
end;
b[1,1]:=-1; b[1,2]:=0;
b[2,1]:=0; b[2,2]:=1;
b[3,1]:=1; b[3,2]:=0;
b[4,1]:=0; b[4,2]:=-1;
close(fv);
for i:=1 to 4 do {首先标记数组C所有元素全部为0}
for j:=1 to 4 do c[i,j]:=0;
c[1,1]:=1;
for i:=1 to 4 do {显示迷宫具体线路,0表示通,1表示不通}
begin
for j:=1 to 4 do
write(a[i,j]:4);
writeln
end;
q:=false; {假设迷宫没有出路}
try(1,1,2);
if q=false then writeln( '此迷宫没有出路')
end.
常用排序算法
一、插入排序(Insertion Sort)
1. 基本思想:
每次将一个待排序的数据元素,插入到前面已经排好序的数列中的适当位置,使数列依然有序;直到待排序数据元素全部插入完为止。
2. 排序过程:
[初始关键字] [49] 38 65 97 76 13 27 49
J=2(38) [38 49] 65 97 76 13 27 49
J=3(65) [38 49 65] 97 76 13 27 49
J=4(97) [38 49 65 97] 76 13 27 49
J=5(76) [38 49 65 76 97] 13 27 49
J=6(13) [13 38 49 65 76 97] 27 49
J=7(27) [13 27 38 49 65 76 97] 49
J=8(49) [13 27 38 49 49 65 76 97]
Procedure InsertSort(Var R : FileType);
//对R[1..N]按递增序进行插入排序, R[0]是监视哨//
Begin
for I := 2 To N Do //依次插入R[2],...,R[n]//
begin
R[0] := R[I]; J := I - 1;
While R[0] < R[J] Do //查找R[I]的插入位置//
begin
R[J+1] := R[J]; //将大于R[I]的元素后移//
J := J - 1
end
R[J + 1] := R[0] ; //插入R[I] //
end
End; //InsertSort //
二、选择排序
1. 基本思想:
每一趟从待排序的数据元素中选出最小(或最大)的一个元素,顺序放在已排好序的数列的最后,直到全部待排序的数据元素排完。
2. 排序过程:
初始关键字 [49 38 65 97 76 13 27 49]
第一趟排序后 13 [38 65 97 76 49 27 49]
第二趟排序后 13 27 [65 97 76 49 38 49]
第三趟排序后 13 27 38 [97 76 49 65 49]
第四趟排序后 13 27 38 49 [49 97 65 76]
第五趟排序后 13 27 38 49 49 [97 97 76]
第六趟排序后 13 27 38 49 49 76 [76 97]
第七趟排序后 13 27 38 49 49 76 76 [ 97]
最后排序结果 13 27 38 49 49 76 76 97
Procedure SelectSort(Var R : FileType); //对R[1..N]进行直接选择排序 //
Begin
for I := 1 To N - 1 Do //做N - 1趟选择排序//
begin
K := I;
For J := I + 1 To N Do //在当前无序区R[I..N]中选最小的元素R[K]//
begin
If R[J] < R[K] Then K := J
end;
If K <> I Then //交换R[I]和R[K] //
begin Temp := R[I]; R[I] := R[K]; R[K] := Temp; end;
end
End; //SelectSort //
三、冒泡排序(BubbleSort)
1. 基本思想:
两两比较待排序数据元素的大小,发现两个数据元素的次序相反时即进行交换,直到没有反序的数据元素为止。
2. 排序过程:
设想被排序的数组R[1..N]垂直竖立,将每个数据元素看作有重量的气泡,根据轻气泡不能在重气泡之下的原则,从下往上扫描数组R,凡扫描到违反本原则的轻气泡,就使其向上"漂浮",如此反复进行,直至最后任何两个气泡都是轻者在上,重者在下为止。
49 13 13 13 13 13 13 13
38 49 27 27 27 27 27 27
65 38 49 38 38 38 38 38
97 65 38 49 49 49 49 49
76 97 65 49 49 49 49 49
13 76 97 65 65 65 65 65
27 27 76 97 76 76 76 76
49 49 49 76 97 97 97 97
Procedure BubbleSort(Var R : FileType) //从下往上扫描的起泡排序//
Begin
For I := 1 To N-1 Do //做N-1趟排序//
begin
NoSwap := True; //置未排序的标志//
For J := N - 1 DownTo 1 Do //从底部往上扫描//
begin
If R[J+1]< R[J] Then //交换元素//
begin
Temp := R[J+1]; R[J+1 := R[J]; R[J] := Temp;
NoSwap := False
end;
end;
If NoSwap Then Return//本趟排序中未发生交换,则终止算法//
end
End; //BubbleSort//
四、快速排序(Quick Sort)
1. 基本思想:
在当前无序区R[1..H]中任取一个数据元素作为比较的"基准"(不妨记为X),用此基准将当前无序区划分为左右两个较小的无序区:R[1..I-1]和R[I+1..H],且左边的无序子区中数据元素均小于等于基准元素,右边的无序子区中数据元素均大于等于基准元素,而基准X则位于最终排序的位置上,即R[1..I-1]≤X.Key≤R[I+1..H](1≤I≤H),当R[1..I-1]和R[I+1..H]均非空时,分别对它们进行上述的划分过程,直至所有无序子区中的数据元素均已排序为止。
2. 排序过程:
初始关键字 [49 38 65 97 76 13 27 49]
第一次交换后 [27 38 65 97 76 13 49 49]
第二次交换后 [27 38 49 97 76 13 65 49]
J向左扫描,位置不变,第三次交换后 [27 38 13 97 76 49 65 49]
I向右扫描,位置不变,第四次交换后 [27 38 13 49 76 97 65 49]
J向左扫描 [27 38 13 49 76 97 65 49]
(一次划分过程)
初始关键字 [49 38 65 97 76 13 27 49]
一趟排序之后 [27 38 13] 49 [76 97 65 49]
二趟排序之后 [13] 27 [38] 49 [49 65]76 [97]
三趟排序之后 13 27 38 49 49 [65]76 97
最后的排序结果 13 27 38 49 49 65 76 97
各趟排序之后的状态
Procedure Parttion(Var R : FileType; L, H : Integer; Var I : Integer);
//对无序区R[1,H]做划分,I给以出本次划分后已被定位的基准元素的位置 //
Begin
I := 1; J := H; X := R[I] ;//初始化,X为基准//
Repeat
While (R[J] >= X) And (I < J) Do
begin
J := J - 1 //从右向左扫描,查找第1个小于 X的元素//
If I < J Then //已找到R[J] 〈X//
begin
R[I] := R[J]; //相当于交换R[I]和R[J]//
I := I + 1
end;
While (R[I] <= X) And (I < J) Do
I := I + 1 //从左向右扫描,查找第1个大于 X的元素///
end;
If I < J Then //已找到R[I] > X //
begin R[J] := R[I]; //相当于交换R[I]和R[J]//
J := J - 1
end
Until I = J;
R[I] := X //基准X已被最终定位//
End; //Parttion //
Procedure QuickSort(Var R :FileType; S,T: Integer); //对R[S..T]快速排序//
Begin
If S < T Then //当R[S..T]为空或只有一个元素是无需排序//
begin
Partion(R, S, T, I); //对R[S..T]做划分//
QuickSort(R, S, I-1);//递归处理左区间R[S,I-1]//
QuickSort(R, I+1,T);//递归处理右区间R[I+1..T] //
end;
End; //QuickSort//
五、堆排序(Heap Sort)
1. 基本思想:
堆排序是一树形选择排序,在排序过程中,将R[1..N]看成是一颗完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系来选择最小的元素。
2. 堆的定义: N个元素的序列K1,K2,K3,...,Kn.称为堆,当且仅当该序列满足特性:
Ki≤K2i Ki ≤K2i+1(1≤ I≤ [N/2])
堆实质上是满足如下性质的完全二叉树:树中任一非叶子结点的关键字均大于等于其孩子结点的关键字。例如序列10,15,56,25,30,70就是一个堆,它对应的完全二叉树如上图所示。这种堆中根结点(称为堆顶)的关键字最小,我们把它称为小根堆。反之,若完全二叉树中任一非叶子结点的关键字均大于等于其孩子的关键字,则称之为大根堆。
3. 排序过程:
堆排序正是利用小根堆(或大根堆)来选取当前无序区中关键字小(或最大)的记录实现排序的。我们不妨利用大根堆来排序。每一趟排序的基本操作是:将当前无序区调整为一个大根堆,选取关键字最大的堆顶记录,将它和无序区中的最后一个记录交换。这样,正好和直接选择排序相反,有序区是在原记录区的尾部形成并逐步向前扩大到整个记录区。
Procedure Sift(Var R :FileType; I, M : Integer);
//在数组R[I..M]中调用R[I],使得以它为完全二叉树构成堆。事先已知其左、右子树(2I+1 <=M时)均是堆//
Begin
X := R[I]; J := 2*I; //若J <=M, R[J]是R[I]的左孩子//
While J <= M Do //若当前被调整结点R[I]有左孩子R[J]//
begin
If (J < M) And R[J].Key < R[J+1].Key Then
J := J + 1 //令J指向关键字较大的右孩子//
//J指向R[I]的左、右孩子中关键字较大者//
If X.Key < R[J].Key Then //孩子结点关键字较大//
begin
R[I] := R[J]; //将R[J]换到双亲位置上//
I := J ; J := 2*I //继续以R[J]为当前被调整结点往下层调整//
end;
Else
Exit//调整完毕,退出循环//
end
R[I] := X;//将最初被调整的结点放入正确位置//
End;//Sift//
Procedure HeapSort(Var R : FileType); //对R[1..N]进行堆排序//
Begin
For I := N Div Downto 1 Do //建立初始堆//
Sift(R, I , N)
For I := N Downto 2 do //进行N-1趟排序//
begin
T := R[1]; R[1] := R[I]; R[I] := T;//将当前堆顶记录和堆中最后一个记录交换//
Sift(R, 1, I-1) //将R[1..I-1]重成堆//
end
End; //HeapSort//
六、几种排序算法的比较和选择
1. 选取排序方法需要考虑的因素:
(1) 待排序的元素数目n;
(2) 元素本身信息量的大小;
(3) 关键字的结构及其分布情况;
(4) 语言工具的条件,辅助空间的大小等。
2. 小结:
(1) 若n较小(n <= 50),则可以采用直接插入排序或直接选择排序。由于直接插入排序所需的记录移动操作较直接选择排序多,因而当记录本身信息量较大时,用直接选择排序较好。
(2) 若文件的初始状态已按关键字基本有序,则选用直接插入或冒泡排序为宜。
(3) 若n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序、堆排序或归并排序。快速排序是目前基于比较的内部排序法中被认为是最好的方法。
(4) 在基于比较排序方法中,每次比较两个关键字的大小之后,仅仅出现两种可能的转移,因此可以用一棵二叉树来描述比较判定过程,由此可以证明:当文件的n个关键字随机分布时,任何借助于"比较"的排序算法,至少需要O(nlog2n)的时间。
(5) 当记录本身信息量较大时,为避免耗费大量时间移动记录,可以用链表作为存储结构。
二分查找法完整版
program jjzx(input,output);
var
a:array[1..10] of integer;
i,j,n,x:integer;
begin
writeln('输入10个从小到大的数:');
for i:=1 to 10 do read(a[i]);
writeln('输入要查找的数:');
readln(x);
i:=1; n:=10; j:=trunc((i+n)/2);
if a[n]
writeln('查找失败,数组中无此元素!')
else
begin
repeat
if a[j]>x then
begin
n:=j; j:=trunc((i+n)/2)
end
else
begin
i:=j+1; j:=trunc((i+n)/2)
end
until (a[j]=x) or (i=j) ;
if a[j]=x then
writeln('查找成功!位置是:',j:3)
else
writeln('查找失败,数组中无此元素!')
end
end.
想想还有其它方法解决X大于数组中的任何一个数的方法这种情况的方法吗?
标准快速排序算法
rogram kuaisu(input,output);
const n=10;
var
s:array[1..10] of integer;
k,l,m,o:integer;
procedure qsort(lx,rx:integer);
var
I,j,t:integer;
Begin
I:=lx;j:=rx;t:=s[I];
Repeat
While (s[j]>t) and (j>I) do
Begin
k:=k+1;
j:=j-1
end;
if I<j then
begin
s[I]:=s[j];I:=I+1;l:=l+1;
while (s[I]<t) and (I<j) do
begin
I:=I+1
End;
If I<j then
begin
S[j]:=s[I];j:=j-1
End;
End;
Until I=j;
S[I]:=t;I:=I+1;j:=j-1; o:=o+1;
writeln('第',o:3,'次排序的结果:');
for m:=1 to 10 do write(s[m]:5);
writeln;
If lx<j then qsort(lx,j);
If I<rx then qsort(I,rx)
End;{过程qsort结束}
Begin
Writeln('input 10 integer num:');
For m:=1 to n do read(s[m]);
K:=0;l:=1; o:=0;
Qsort(l,n);
Writeln('shu chu jie guo:');
For m:=1 to n do write(s[m]:4) ;
End.
一躺快速排序法
{快速排序法的一躺排序程序}
program kuaisu(input,output);
type
arr=array[1..7] of integer;
var
a:arr;
i,j,k:integer;
procedure sort(var a:arr;var m,n:integer);
var
x,p,q:integer;
begin
x:=a[m];
repeat
while ((m<n) and (a[n]>x)) do n:=n-1;
p:=a[m];a[m]:=a[n];a[n]:=p;
while ((m<n) and (a[m]<x)) do m:=m+1;
p:=a[m];a[m]:=a[n];a[n]:=p
until m=n
end;
begin
writeln('input 10 integer num:');
i:=1;j:=1;k:=7;
repeat
read(a[i]);
i:=i+1;
until i>7;
sort(a,j,k);
for i:=1 to 7 do
write(a[i]:4);
writeln('j=',j:4,'k:',k:4)
end.
快速排序算法
快速排序是对冒泡排序的一种改进。它的基本思想是:通过一躺排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一不部分的所有数据都要小,然后再按次方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
假设要排序的数组是A[1]……A[N],首先任意选取一个数据(通常选用第一个数据)作为关键数据,然后将所有比它的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一躺快速排序。一躺快速排序的算法是:
1)、设置两个变量I、J,排序开始的时候I:=1,J:=N;
2)以第一个数组元素作为关键数据,赋值给X,即X:=A[1];
3)、从J开始向前搜索,即由后开始向前搜索(J:=J-1),找到第一个小于X的值,两者交换;
4)、从I开始向后搜索,即由前开始向后搜索(I:=I+1),找到第一个大于X的值,两者交换;
5)、重复第3、4步,直到I=J;
例如:待排序的数组A的值分别是:(初始关键数据X:=49)
A[1] A[2] A[3] A[4] A[5] A[6] A[7]:
49 38 65 97 76 13 27
进行第一次交换后: 27 38 65 97 76 13 49
( 按照算法的第三步从后面开始找<X的值,27
进行第二次交换后: 27 38 49 97 76 13 65
( 按照算法的第四步从前面开始找>X的值,65>49,两者交换,此时I:=3 )
进行第三次交换后: 27 38 13 97 76 49 65
( 按照算法的第五步将又一次执行算法的第三步从后开始找<X的值,13
进行第四次交换后: 27 38 13 49 76 97 65
( 按照算法的第四步从前面开始找大于X的值,97>49,两者交换,此时J:=4 )
此时再执行第三不的时候就发现I=J,从而结束一躺快速排序,那么经过一躺快速排序之后的结果是:27 38 13 49 76 97 65,即所以大于49的数全部在49的后面,所以小于49的数全部在49的前面。
快速排序就是递归调用此过程——在以49为中点分割这个数据序列,分别对前面一部分和后面一部分进行类似的快速排序,从而完成全部数据序列的快速排序,最后把此数据序列变成一个有序的序列,根据这种思想对于上述数组A的快速排序的全过程如图6所示:
初始状态 {49 38 65 97 76 13 27}
进行一次快速排序之后划分为 {27 38 13} 49 {76 97 65}
分别对前后两部分进行快速排序 {13} 27 {38}
结束 结束 {49 65} 76 {97}
49 {65} 结束
结束
图6 快速排序全过程
1)设有N(假设N=10)个数,存放在S数组中;
2)在S[1。。N]中任取一个元素作为比较基准,例如取T=S[1],起目的就是在定出T应在排序结果中的位置K,这个K的位置在:S[1。。K-1]<=S[K]<=S[K+1..N],即在S[K]以前的数都小于S[K],在S[K]以后的数都大于S[K];
3)利用分治思想(即大化小的策略)可进一步对S[1。。K-1]和S[K+1。。N]两组数据再进行快速排序直到分组对象只有一个数据为止。
如具体数据如下,那么第一躺快速排序的过程是:
数组下标: 1 2 3 4 5 6 7 8 9 10
45 36 18 53 72 30 48 93 15 36
I J
(1) 36 36 18 53 72 30 48 93 15 45
(2) 36 36 18 45 72 30 48 93 15 53
(3) 36 36 18 15 72 30 48 93 45 53
(4) 36 36 18 15 45 30 48 93 72 53
(5) 36 36 18 15 30 45 48 93 72 53
通过一躺排序将45放到应该放的位置K,这里K=6,那么再对S[1。。5]和S[6。。10]分别进行快速排序。程序代码如下:
program kuaisu(input,output);
const n=10;
var
s:array[1..10] of integer;
k,l,m:integer;
procedure qsort(lx,rx:integer);
var
I,j,t:integer;
Begin
I:lx;j:rx;t:s[I];
Repeat
While (s[j]>t) and (j>I) do
Begin
k:=k+1;
j:=j-1
end;
if I<j then
begin
s[I]:=s[j];I:=I+1;l:=l+1;
while (s[I]<t) and (I<j) do
begin
k:=k+1;
I:=I+1
End;
If I<j then
begin
S[j]:=s[I];j:=j-1;l:=l+1;
End;
End;
Until I=j;
S[I]:=t;I:=I+1;j:=j-1;l:=l+1;
If lx<j then qsort(lx,j);
If I<rx then qsort(I,rx)
End;{过程qsort结束}
Begin
Writeln('input 10 integer num:');
For m:=1 to n do read(s[m]);
K:=0;l:=0;
Qsort(l,n);
Writeln('排序后结果是:');
For m:=1 to n do write(s[m]:4)
End.
插入排序算法
通过学习上述两种方法可以了解排序的基本思想,也可以对任何一个无序数组作出从大到小(降序)或从小到大(升序)的排列。现在假设有一个已经有序的数据序列,要求在这个已经排好的数据序列中插入一个数,但要求插入后此数据序列仍然有序,这个时候就要用到一种新的排序方法——插入排序法,插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据。
题目:A数组中有N个数据,按从小到大的顺序排列,输入一个数X,把X的值插入到数组A中,使得插入后的A数组仍然按从小到大排列。
那么这个问题的解决算法就是:
1)、通过比较大小找到X应插入的位置,假如应该放在第I个位置;
2)、把从I开始的(包括I)的所有数组元素依次向后移动一个位置,即A[I+1]:=A[I];
3)、A[I]:=X;
通过上述算法可以写出插入排序算法的程序流程图,如图5所示:
|
开 始 |
|
读入一个数到X中;I:=1 |
|
A[I]>X |
|
N |
|
I:= I+1 |
|
I>N--1 |
|
Y |
|
N |
|
Y |
|
A[J]:=A[J-1] ;=A[I] |
|
J=I |
|
J:=J--1 |
|
J:=N+1 |
|
A[I]:=X |
|
结 束 |
图5 插入排序算法程序流程图
按照上述流程图写出的代码如下:
program charu(input,output);
var
a:array[1..11] of integer;
x,I,j,n:integer;
f:Boolean;
Begin
{给数组赋一个已经排序好的初值,设A[1]到A[10]分别等于1到10}
For I:=1 to 10 do A[I]:=I;
Writeln(‘数组原来的排列值是:’);
For I:=1 to 10 do write(a[I]:4);
Writeln;
Writeln(‘输入一个整数:’);
Readln(x);
F:=false;
I:=0;N:=10;
Repeat
I:=I+1;
If a[I]>x then f:=ture;
Until I>n or f=ture;
J:=n+1;
While j<>I do
begin
A[j]:=a[j-1];
J:=j-1
End;
A[I]:=x;
Writeln(‘ 插入一个数后的数组排列值是:’);
For I:=1 to n+1 do
Write(a[I]:4)
End.
注意:此排序方法要求数组一定要有足够的预留空间来容纳插入后的数据。
选择排序算法
在介绍选择排序法之前先介绍一种把最小的数放在第一个位置上的算法,当然也可以用前面所讲的冒泡排序法,现在我们改用一种新的算法:其指导思想是先并不急于调换位置,先从A[1]开始逐个检查,看哪个数最小就记下该数所在的位置P,等一躺扫描完毕,再把A[P]和A[1]对调,这时A[1]到A[10]中最小的数据就换到了最前面的位置。算法的步骤如下:
1)、先假设A[1]中的数最小,记下此时的位置P=1;
2)、依次把A[P]和A[I](I从2变化到10)进行比较,每次比较时,若A[I]的数比A[P]中的数小,则把I的值赋给P,使P总是指向当前所扫描过的最小数的位置,也就是说A[P]总是等于所有扫描过的数最小的那个数。在依次一一比较后,P就指向10个数中最小的数所在位置,即A[P]就是10个数中最小的那个数;
3)把A[P]和A[1]的数对调,那么最小的数就在A[1]中去了,也就是在最前面了。
如果现在重复此算法,但每重复一次,进行比较的数列范围就向后移动一个位置。即第二遍比较时范围就从第2个数一直到第N个数,在此范围内找最小的数的位置P,然后把A[P]与A[2]对调,这样从第2个数开始到第N个数中最小数就在A[2]中了,第三遍就从第3个数到第N个数中去找最小的数,再把A[P]与A[3]对调……此过程重复N-1次后,就把A数组中N个数按从小到大的顺序排好了。这种排序的方法就是选择排序法。以上算法可以用图4表示:
|
J>N--1 |
|
J:=1 |
|
P:=J |
|
I>N |
|
A[I]<A[P] |
|
I:=J+1 |
|
I:=I+1 |
|
P:=I |
|
N |
|
Y |
|
N |
|
Y |
|
结 束 |
|
Y |
|
A[P]与A[J]对调;J:=J+1 |
|
N |
图4 选择排序法程序流程图
程序代码:
program xuanze(input,output);
const n=10;
type
colarr=array[1..n] of integer;
var
a:colarr;I,j,p,t:integer;
begin
writeln(‘input 10 integer num:’);
for I:=1 to n do read(a[I]);
for j:=1 to n-1 do
begin
p:=j
for I:=j+1 to n do if a[I]<a[p] then p:=I;
t:=a[p];a[p]:=a[j];a[j]:=t;
end;
writeln(‘output num:’);
for I:=1 to n do
write(a[I]:5)
end.
冒泡排序算法
在解释冒泡排序算法之前,先来介绍把10个数(放在数组A中)中最大的那个数放在最后位置上的一种算法。算法描述如下:
(1)从数组A[1]到A[10],把相临的两个数两两进行比较。即A[1]和A[2]比较,比较完后A[2]再与A[3]比较,……最后是A[9]和A[10]比较。
(2)在每次进行比较的过程中,如果前一个数比后一个数大,则对调两个数,也就是说把较大的数调到后面,较小的调到前面。比如在第一次的比较中,如果A[1]比A[2]大则A[1]和A[2]的值就互换。下图用6个数据来说明以上的算法。
假设6个数据是:A[]=5 7 4 3 8 6
A[1] A[2] A[3] A[4] A[5] A[6]
5 7 4 3 8 6 第一次,A[1]=5和A[2]=7比较,7>5,不进行对调。
5 7 4 3 8 6 第二次,A[2]=7和A[3]=4比较,4<7,进行对调,
那么第二次比较完后的数据是5 4 7 3 8 6
5 4 7 3 8 6 第三次,A[3]=7和A[4]=3比较,3<7,进行对调,
那么第三次比较完后的数据是5 4 3 7 8 6
5 4 3 7 8 6 第四次,A[4]=7和A[5]=8比较,8>7,不进行对调。
5 4 3 7 8 6 第五次,A[6]=6和A[5]=8比较,6<8,进行对调,
那么第五次也就是最后一次的结果是
5 4 3 7 6 8
由上例可以看出,对于6个数,排好一个数(最大的数)需要进行5次比较,可以推断出,对于N个数,一躺需要 N-1次比较操作,算法流程图如图2所示:
|
I:=1 |
|
A[I]>A[I+1] |
N
Y
|
A[I]与A[I+1]对调 |
|
I:=I+1 |
|
I>N-1 |
N
Y
|
结 束 |
|
图2 求最大数算法流程图 |
上述算法已经把N个数中最大的数放到了A[N]中,再重复上述算法,把A[1]到A[N-1]中最大的数放到A[N-1]中,这样A[N-1]中存放的就是第二大的数,接着把A[1]到A[N-2]中最大的数放到A[N-2]中,……最后把A[1]到A[2]中大的那个数放到A[2]中,每重复一次两两比较后,比较的范围就朝前移动一个位置,此算法经过N-1次就完成了A[1]到A[N]中的的数由小到大的排列。
注意:如果要由大到小排列则在比较时前一个数比后一个数小就进行对调,方法相反。
由此可以看出,冒泡法的基本思想就是:在待排序的数据中,先找到最小(大)的数据将它放到最前面,再从第二个数据开始,找到第二小(大)的数据将它放到第二个位置,以此类推,直到只剩下最后一个数为止。这种排序方法在排序的过程中,是小的数就如气泡一样逐层上浮,而使大的数逐个下沉,于是就形象地取名为冒泡排序,又名起泡排序。
冒泡排序可用图3所示的流程图表示:
|
开 始 |
|
J:=1 |
|
I:=1 |
N
|
A[I]>A[I+1] |
|
A[I]与A[I+1]对调 |
Y
|
I:=I+1 |
|
I>N--J |
N
Y
|
J:=J+1 |
|
N |
|
J>N--1 |
Y
|
结 束 |
图3 冒泡排序算法程序流程图
程序举例:
程序要求: 从键盘输入十个数,然后按从小到大的顺序输出。
程序代码:
program bubble(input,output);
const n=10;
type
colarr=array[1..n] of integer;
var
a:colarr; t,i,j:integer;
begin
writeln(‘INPUT 10 integer num:’);
for I:=1 to n do read(a[I]);
readln;
for j:=1 to n-1 do
for I:=1 to n-j do
if a[I]>a[I+1] then
begin
t:=a[I];a[I]:=a[I+1];a[I+1]:=t
end;
writeln(‘OUTPUT new num:’);
for I:=1 to n do
begin
write(a[I]:4);
if I mod 5=0 then writeln
end;
end.
程序运行结果如下:
input 10 integer num: {提示输入10个数}
7 6 23 21 19 18 10 16 5 13 {假如输入的数据}
output new num: {冒泡排序后输出结果}
5 6 7 10 13
16 18 19 21 23
问题:假如要从从大到小输出程序应该怎么改?
两重循环总共执行了多少次?
统计算法
作用:在给定的范围内求出符合设定条件的记录个数。
算法基本思想:用一个条件语句判断当前记录是否符合给定条件,符合则统计个数加一。用循环实现对所有记录的操作。
程序基本设计流程图如图1所示:
|
开 始 |
|
读入一个记录 |
|
符合条件 |
N
|
统计个数加一 |
Y
|
全部读完 |
N
Y
|
输出统计个数 |
|
结 束 |
图1 统计算法程序流程图
举例说明:
例一、 从键盘敲进任意个(少于255个)字符,然后求出其中某一个字母的个数(如大写字母A)。
分析:用一个字符串变量来接受从键盘输入的字符,然后从第一个字符开始对每一个字符进行处理,如果是A则个数加一,最后把总的统计个数输出。
程序代码:
program jjzx(input,output);
type
str=string[255];
var
st:str;
n,i,j:integer;
begin
writeln(‘请输入一行字符: ‘);
readln(st);
j:=lenth(st); {把字符串的实际长度赋给j}
n:=0;
for i:=1 to j do
if ord(st[i])=65 then n:=n+1;
writeln(‘你输入的字符是: ‘,st);
writeln((‘其中字符A的个数和: ‘,n)
end.
常用算法——广度优先搜索
在深度优先搜索算法中,是深度越大的结点越先得到扩展。如果在搜索中把算法改为按结点的层次进行搜索,本层的结点没有搜索处理完时,不能对下层结点进行处理,即深度越小的结点越先得到扩展,也就是说先产生 的结点先得以扩展处理,这种搜索算法称为广度优先搜索法。英语中用Breadth-First-Search表示,所以我们 也把广度优先搜索法简称为BFS。
1、广度优先搜索的基本思想
从图中某一顶点Vo出发,首先访问Vo相邻的所有未被访问过的顶点V1、V2、……Vt;再依次访问与V1、V2、……Vt相邻的且未被访问过的所有顶点。如此继续,直到访问完图中所有的顶点。
如果用广度优先法对下图中结点进行搜索,从结点V1出发,先搜索处理它的子结点V2和V3,即深度为2的结点;然后搜索深度为3的子结点V4、V5、V6、V7;最后搜索深度为4的 结点V8和V9。整个搜索的次序与结点产生的次序完全一致。
深度
__V1__ 1
/ \
V2 V3 2
/ \ / \
V4 V5 V6 V7 3
/ \
V8 V9 4
2.广度优先搜索基本算法:
1)从某个顶点出发开始访问,被访问的顶点作相应的标记,并输出访问顶点号;
2)从被访问的顶点出发,依次搜索与该顶点有边的关联的所有未被访问的邻接点,并作相应的标记。
3)再依次根据2)中所有被访问的邻接点,访问与这些邻接点相关的所有未被访问的邻接点,直到所有顶点被访问为止。
【算法过程】
procedure guangdu(i);
begin
write(i);
v[i]:=true;
insert(q,i);{q是队列,i进队}
repeat
k:=delete(q);{出队}
for j:=1 to n do
if (a[k,j]=1) and (not v[j]) then
begin
write(j);
v[j]:=true;
insert(q,j);
end;
until 队列q为空;
【实际应用】:实际应用的算法流程图通常如下:
【问题描述】如下图,找出C1到C6的一条最短路径并求出其路程总长度(采用广度优先搜索的顶点访问序列为C1,C2,C3,C4,C5,C6)。
【Pascal程序】
program tu3bfs;
type fg=set of 1..6;
const link:array[1..5,1..6] of integer=((0,4,8,0,0,0),
(4,0,3,4,6,0),(8,3,0,2,2,0),(0,4,2,0,4,9),(0,6,2,4,0,4));
var pnt,city:array[1..10] of 0..6;
flag:fg;
r,k,head,tail:integer;
procedure print;
var n, i,cost,y:integer;
s:array[1..7] of 1..6;
begin
y:=tail;n:=0; cost:=0;
while y>0 do begin inc(n);s[n]:=y;y:=pnt[y] end;
writeln('minpath=',n-1);
write('1');
for i:=n-1 downto 1 do
begin
write('->',s[i]);
cost:=cost+link[s[i+1],s[i]];
end;
writeln;
writeln('cost=',cost);
end;
begin
flag:=[1];
pnt[1]:=0; city[1]:=1;
head:=0;tail:=1;
repeat
head:=head+1;
k:=city[head];
for r:=2 to 6 do
if not(r in flag) and (link[k,r]>0) then
begin
inc(tail);city[tail]:=r;
pnt[tail]:=head;
flag:=flag+[r];
if r=6 then begin print;halt end;
end;
until head>=tail;
readln;
end.
常用算法——深度优先搜索
我们在对一些问题进行求解时,会发现有些问题很难找到规律,或者根本无规律可寻。对于这样的问题,可以利用计算机运算速度快的特点,先搜索查找所有可能出现的情况,再根据题目条件从所有可能的情况中,删除那些不符合条件的解。
【例题1】 有A、B、C、D、E 5本书,要分给张、王、刘、赵、钱5位同学,每人只能选1本。每个人都将自己喜爱的书填写在下表中。请你设计一个程序,打印出让每个人都满意的所有分书方案。
┌──┬───┬───┬───┬───┬───┐
││A │ B │ C │ D │ E │
├──┼───┼───┼───┼───┼───┤
│张│││√│√││00110
├──┼───┼───┼───┼───┼───┤
│王│√│√│││√│11001
├──┼───┼───┼───┼───┼───┤
│刘││√│√│││01100
├──┼───┼───┼───┼───┼───┤
│赵││││√││00010
├──┼───┼───┼───┼───┼───┤
│钱││√│││√│01001
└──┴───┴───┴───┴───┴───┘
★问题分析
题目中每人喜爱哪本书是随意的,无规律可循,所以用穷举方法解较为合适。按穷举法的一般算法,可以暂不考虑一些条件,先求出满足部分条件的解,即可行解。然后,再加上尚未考虑的条件,从可行解中删除不符合这些条件的解,留下的就是问题的解。具体到本题中,我们可以先不考虑“让每人都满意”这个条件,这样,就只剩“每人选一本且只能选一本”这一个条件了。在这个条件下,可行解是5本书的所有全排列,一共有5!=120种情况。从这120种可行解中删去不符合“每人都满意”这一条件的解,剩下的就是本题的解。
为编程方便,我们用1、2、3、4、5分别表示这5本书。这5个数字的—种全排列就是5本书的一种分法。例如54321就表示第五本书(即E)分给张,第四本书(即D)分给王……,第—本书(即A)分给钱。
每个人“喜爱书表”,在程序中我们用二维数组Like[i,j]来表示,1表示喜爱,0表示不喜爱。排列的产生可以用穷举法,也可以用专门算法。
★算法设计:
第一步:产生5个数字的一个全排列;
第二步:检查所产生的全排列是否符合“喜爱书表”,如果符合就输出;
第三步:检查是否所有排列都产生了,如果没有产生完,则返回第一步;
第四步:结束。
根据题目给出的条件,还可以对上面算法进行一些改进。例如产生一个全排列12345时,第一个数1表示将第一本书给小张。但从表中可以看出,这是不可能的,因为小张只喜欢第三、第四本书。也就是说,1X X X X这一类分法是不符合条件的。由此使我们想到,如果选定第一本书后,就立即检查一下是否符合条件,当发现第一个数的选择不符合条件时,就不必再产生后面的4个数了,这样做可以减少很多的运算量。换句话说,第一个数只在3和4中选择,这样就可以减少3/5的运算量。同理,在选定了第一个数后,其他4个数字的选择也可以用类似的方法处理,即选择第二个数后,立即检查是否符合条件。例如,第一个数选3,第二个数选4后,立即进行检查,发现不符合条件,就另选第二个数。这样就又把34XXX一类的分法删去了,从而又减少了一部分运算量。
综上所述,改进后本题算法应该是:在产生各种排列时,每增加一个数字,就检查一下该数的加入是否符合条件,如不符合,就立刻换一个;若符合条件,则再产生下一个数。因为从第i本书到第i+1本书的寻找过程是相同的,所以可以用递归方法编程。
★算法框图
PROCEDURE TRY(i);(递归算法)
┌─────────────────────┐
│For j:= 1 to 5 do │
├─┬───────────────────┤
││T\第I个学生喜爱第j本书/F│
│├────────────┬──────┤
││记录第 i个数││
│├────────────┤│
││\i= 5/││
││T\/ F ││
│├─────┬──────┤│
││打印一个解│Try(i+1)││
│├─────┴──────┤│
││删去第i 个数字││
└─┴────────────┴──────┘
我们用二维数组like存放“喜爱书表”,用集合flag存放已分出书的编号,数组book存储各人所分得书的编号,如book[1]=3,则表示第一个同学(小张)分得编号为3的书。
递归程序如下(程序中将小张的喜欢的书改成了ACD):
Program allot_book(output);
type five=1..5;
const like: array[five,five] of 0..1 =((1, 0, 1,1 ,0),
(1,1,0,0,1),(0,1,1,0,0),(0,0,0,1,0),(0,1,0,0,1));
{个人对各种书的喜好情况}
name:array[five] of string[5] =
('zhang', 'wang','liu', 'zhao', 'qian' );
{数组name存放学生姓名}
var book: array[1..5] of 0..5;{存放各人分配到的书的编号}
flag: set of five;
c: integer;
procedure print; {打印分配方案}
var i: integer;
begin
inc(c); {计数,统计得到分配方案数}
writeln( 'answer', c,':');
for i:=1 to 5 do
writeln(name[i]: 10,':', chr(64 + book[i] ) );
end;
procedure try(i: integer); {判断第 I 个学生分得书的编号}
var j: integer;
begin
for j:=1 to 5 do
if not(j in flag) and (like[i,j]>0) then
begin {第j本书未选过,且第I个学生喜爱第j本书}
flag:= flag + [j]; {修改已选书编号集合,加入第j本书}
book[i]:=j; {记录第 I 个学生分得书的编号}
if i= 5 then print {I = 5,5 个学生都分到自己喜爱的书}
else try(i + 1);
{i<5,继续搜索下一个学生可能分到书的情况}
flag:= flag - [j]; {后退一步,以便查找下一种分配方案}
book[i]:=0;
end
end;
{ main prg }
begin
flag:= [];
c:=0;
try(1);
readln
end.
运行结果为:
zhang: C
wang: A
liu:B
Zhao: D
qian: E
另外,此题也可以用非递归的算法解。非递归算法的基本思想是用栈来存放被选中书的编号。设dep表示搜索深度,r为待选书号,p为搜索成功标志。算法表示如下(非递归算法)。
PROCEDURE dfs;(非递归算法)
┌────────────────────────────┐
│Dep:=0│
├─┬──────────────────────────┤
││dep:=dep+1│
│├──────────────────────────┤
││j:=0; p:=False;│
│├─┬────────────────────────┤
│││j:=j+1│
││├────────────────────────┤
│││T\子结点mr符合统计/F│
││├──────────────┬─────────┤
│││产生子结点,并记录│T\Mxar/F│
││├──────────────┼────┬────┤
│││T\子结点是目标/F│回溯│P:=Fatse│
││├──────┬───────┤││
│││输出并出栈│P:= true│││
│├─┴──────┴───────┴────┴────┤
││UNTIL p=True│
├─┴──────────────────────────┤
│UNTIL dep= 0│
└────────────────────────────┘
尽管深度优先基本算法类似,但在处理不同问题时,在具体处理方法、编程的技巧上,却不尽相同;有时甚至会有很大的差别。
比如,例1的解法还可以这样来设计:从表中看出,赵同学只喜爱D这一本书,无其它选择余地。因此,赵同学得到书的编号在搜索前就确定下来了。为了编程方便,可以把赵钱2人位置交换,这样程序只需对张王刘钱4人情况进行搜索测试。
另外,发现表示“喜爱书表”的数组有多个0,为减少不必要的试探,我们改用链表来表示。例如第三位同学的链表是:Like[3,0]=2.Like[3,2]=3.Like[3,3]=0,其中,Like[3,0]=2表示他喜爱的第一本书编号是2,Like[3,2]=3即表示喜爱的编号为2的书后面是编号为3的书,Like[3,3]=0,表示编号为3的书是其最后1本喜爱的书。
这样基本算法不变,但程序改进如下:
Program allot_book(output); {linking List}
type five=1..5;{将小张的喜欢的书改成了ACD}
const Link: Array[ 1..5,0..5 ] of 0..5 =
((1,3,0,4,0,0),(1,2,5,0,0,0),(2,0,3,0,0,0),(4,0,0,0,0,0),(2,0,5,0,0,0));
{个人对各种书的喜好情况}
name:array[five] of string[5] =
('zhang', 'wang','liu', 'zhao', 'qian' );
{数组name存放学生姓名}
var book: array[1..5] of 0..5;{存放各人分配到的书的编号}
flag: set of five;
c: integer;
procedure print; {打印分配方案}
var i: integer;
begin
inc(c); {计数,统计得到分配方案数}
writeln( 'answer', c,':');
for i:=1 to 5 do
writeln(name[i]: 10,':', chr(64 + book[i] ) );
end;
procedure try(i: integer); {判断第 I 个学生分得书的编号}
var j: integer;
begin
j:=0;
repeat
j:=link[i,j]; { 取链表中喜爱书编号j }
If not(j in flag) and (j>0) then
Begin
flag:= flag+ [j];
book[i]:=j;
if i=5 then print
else try(i + 1);
flag:= flag - [j]; {后退一步,以便查找下一种分配方案}
book[i]:=0;
End;
until j = 0;
end;
{ main prg }
begin
flag:= [];
c:=0;
try(1);
readln
end.

浙公网安备 33010602011771号