p25集合
一、集合的概念
集合是由一些同类型的对象汇集在一起形成的,从这点上讲集合跟数组是相同的,不同的是集合中的元素的个数是可变的,而且集合只作为一个整体来使用,不能单独使用集合中的元素,Pascal语言中的集合类型,同数学中集合的概念一样。具体地说,一个集合就是由同一种有序类型的一组数据元素所组成的,这一种有序类型称为该集合的基类型。
集合一般用一对方括号表示,如:[red,black,white,blue,green,yellow]。
其中,red,black,white,blue,green,yellow称为集合的元素。在集合中,各元素之间用逗号隔开。在一个集合中可以没有任何元素,这样的集合称为空集合,用[ ]表示。
在使用集合时要注意以下几点:
(1) 集合的值与方括号内元素出现的次序无关。如[1,2,3,4] 和[1,2,4,3]是两个相等的集合;
(2) 在集合中,同一元素的重复出现对集合的值没有影响。如[1,2,3,1,1,2]和[1,2,3]也是两个相等的集合;
(3) 在集合中,如果元素的值是连续的,则可以用子界类型来表示。如[1,2,3,4,5,8,9,10,15,21,22,23,24],这个集合可以表示成[1..5,8..10,15,21..24];
(4) 集合的基类型可以是任何顺序类型,包括整型、字符型、枚举型、子界型,但不能为实型或其他构造类型。
(5) 每个元素均可用基类型所允许的表达式来表示。如:[1+10,4*2,3..6]。
二、集合类型的定义格式为:
type 标识符=set of 基类型;
例如:type num1=set of 1..10;
num2=set of integer;
c1=set of ‘A’..’Z’;
c2=set of char;
weekday=(sun,mon,tue,wed,thur,fri,sat);
colors=(red,black,white,blue,green,yellow);
fruits=(orange,banana,apple);
c3=set of weekday;
c4=set of colors;
c5=set of fruits;
这样就定义了7个集合,num1是数字1到数字10的数字集合类型,num2是整数集合类型,c1是字符’A’到字符’Z’的字符集合类型,c2是所有字符的集合类型,c3,c4,c5都是枚举集合类型,它们分别表示一周的7天、6种颜色和4种水果。
有了以上的集合类型定义后,我们就可以定义集合型变量了。如:
var n:num1;
ch:c1;
也可以把集合类型的定义和集合变量的定义合并在一起。如:
var n:set of 1..10;
ch:set of ‘A’..’Z’;
当一个集合变量允许包含有n个元素时,不考虑有重复元素的情况,则所构成的集合有多少种可能?不难算出应该是2n种。
例如:type numset=set of 1..3;
var n:numset;
则集合n的值有8种可能,分别为:[ ],[1],[2],[3],[1,2],[1,3],[2,3],[1,2,3]。
三、集合的赋值
对集合变量的赋值只能通过赋值语句来完成。而不能通过输入语句(read或readln)来进行。例如,假设在程序的说明部分已包含以下一行:
var ch1,ch2:set of ‘A’..’Z’;
则,在程序的执行部分,只能通过赋值语句对ch1,ch2赋值,如:
ch1:=[ ];ch2:=[‘A’..’D’];
而不能用read(ch1,ch2);
另外还要注意,赋值号的右边必须用[‘A’..’D’],而不能写成ch2:=’A’..’D’;因为赋值号的左边是集合型变量,根据赋值相容原理,赋值号右边也必须是一个集合(或集合表达式)。
四、集合运算的实现
1集合之间的并、交、差运算
假设有A和B两个集合,A=[1,3,4,5],B=[4,5,6,7],则A和B可以进行三种集合运算,见下表
运算符 | 运算名称 | 表示形式 | 运算方法 | 运算结果 |
+ | 并 | A+B | 取两个集合中不重复的所有元素。 | [1,3,4,5,6,7] |
* | 交 | A*B | 取两个集合中的相同元素 | [4,5] |
- | 差 | A-B | 取在集合A中但又不在集合B中的所有元素。 | [1,3] |
例如:
[1,2,4]+[3]=[1,2,3,4]; [2,3,4]*[1,2,3]=[2,3]; [1,2,3,4]-[2]=[1,3,4]
集合之间也可以进行关系运算,具体的使用方法如下表所示:
关系运算符 | 关系运算名称 | 关系运算的含义 |
= | 相同 | 检查两个集合所包含的元素相同 |
<> | 不相同 | 检查两个集合不相等 |
<= | 包含于 | 检查第一个集合中的元素是否都在第二个集合中出现 |
>= | 包含 | 检查第一个集合中的元素是否包含第二个集合中的所有元素 |
in | 属于 | 检查集合基类型的一个元素是否属于集合 |
注意:并、交、差运算都是针对两个集合的,旦运算结果也是一个集合。另外,集合的并(+)、交(*)、差(-)运算形式上与算术运算的加(+)、乘(*)、减(-)是一样的,但含义却完全不同。
2.集合之间的关系运算
两个集合之间可以进行相等或不相等、包含或被包含的关系运算,关系运算的结果均为布尔值。假设有A和B两个集合,A=[1,3,4,5],B=[1,3,4],则这四种关系运算的含义如下表:
运算符 | 名称 | 表示形式 | 定义 | 运算结果 |
= | 相等 | A=B | 测试两个集合是否相等 | false |
<> | 不相等 | A<>B | 测试两个集合是否不相等 | true |
<= | 包含于 | A<=B | 测试集合A中的元素是否都包含于集合B中 | false |
>= | 包含 | A>=B | 测试集合A是否包含集合B中的所有元素 | true |
3、in运算
在程序设计过程中,我们经常需要检查一个元素是否在指定的集合中,从而对这个集合做进一步的操作,如“添加一个元素到集合中”或“删除集合中的一个元素”。pascal为我们提供了“属于集合”运算符——in,它是用来判断元素与集合的关系的,运算结果也是布尔型。
举例来说,假设集合a=[1..5],x为integer,要求判断x是否在集合a中,若在则删除a中的x这个元素,若不在则把x在个元素添加到集合a中,程序段如下:
if x in a then a:=a-[x]
else a:=a+[x];
集合变量的输出也不能直接用write或writeln语句,往往也是通过in运算。例如,假设集合a=[1,3,5,7,9,11,13,15,17,19],x为integer,要求输出20之内的奇数,程序段如下:
for x:=1 to 20 do
if x in a then write(x:3);
下面是一段关于集合输入输出的参考程序:
Type
Weekday=(Sunday,Monday,tusday,Wednesday,Thursday,Friday,saturday);
Dayset=set of weekday;
Const
Daynames:array[weekday] of string;
Var
Workday:dayset;
Day:weekday;
Separator:char;
Begin
……….
Write(‘[’);
Separator:=’ ‘;
For day:=Sunday to Saturday do
If day in workday
Then
Begin
Write(separator,daynames[day]);
Separator:=’,’;
End;
Write(‘]’)
End.
例1 输入一串字符,以“?”结束,组成元音字母集合、辅音字母集合,然后输出两集合元素及其元素个数。
Program aa;
Var
S1,s2:set of ‘a’..’z’;
N1,n2:integer;
Ch:char;
Begin
S1:=[ ];
S2:=[ ];
N1:=0;
N2:=0;
Read(ch);
While ch<>’?’ do
Begin
If ch in[‘a’..’z’]
Then if ch in [‘a’,’e’,’i’,’o’,’u’]
Then s1:=s1+[ch]
Else s2:=s2+[ch];
Read(ch);
End;
For ch:=’a’ to ‘z’ do
If ch in s1
Then
Begin
Write(ch);
N1:=n1+1;
End;
Writeln;
Writeln(‘n1=’,n1);
For ch:=’a’ to ‘z’ do
If ch in s2
Then
Begin
Write(ch);
N2:=n2+1;
End;
Writeln;
Writeln(‘n2=’,n2);
End.
例2调用随机函数产生10个互不相同的随机整数(0≦x≦40),放入集合中并一起输出(5个一行)。
程序代码如下:
program aa;
var
a:set of 0..40;
i,m,n:integer;
begin
a:=[ ];n:=0;
randomize; {初始化随机数发生器}
repeat
m:=random(41); {随机数发生器}
if not (m in a) then begin a:=a+[m];
n:=n+1;
end;
until n=10;
n:=0;
for i:=0 to 40 do
if i in a then begin write(i:4);
n:=n+1;
if (n mod 5)=0 then writeln
end;
end.
注意:在使用Random之前需要使用Randomize语句进行随机数种子的初始化。
例如:
var
a,i,,j:integer;
begin
{RANDOMIZE;}
for j:=1 to 2 do
begin
for i:=1 to 10 do
begin
a:=random(1000);
writeln(a);
end;
writeln;
end;
end.
这两组数据输出的是一样的结果,如果在程序前加上RANDOMIZE(即把大括号去掉)再运行程序,输出数据就不一样了.
RANDOM产生的是伪随机数或者说是用一种复杂的方法计算得到的序列值,因此每次运算时需要一个不同的种子值。种子值不同,得到的序列值也不同。因此也就是真正的随机数了。这也正是RANDOMIZE随机初始化的作用。
例3、用筛选法求n以内的所有素数(包括n,输出时5个一行)。
问题分析:筛法是一种高效地求素数的方法,它是由希腊著名数学家埃拉托色尼首先提出的,具体算法如下:
假设用集合变量sieve表示筛子,用集合变量primes存放结果(n以内的所有素数);
① 初始化:将所有侯选数放入筛中,即sieve:=[2..n],同时给primes:=[ ];
② 找出筛中的最小数next,next必定为素数;{请同学们思考为什么?}
③ 将next这个数存入primes中,同时将它的所有倍数从sieve筛去;
④ 重复②~③,直到筛空(sieve=[ ])。
程序代码如下:
program aa;
const n=100;
var sieve,primes:set of 1..n;
next,j:integer;
begin
sieve:=[2..n];
primes:=[ ];
next:=2;
repeat
while not(next in sieve) do next:=next+1;
primes:=primes+[next];
j:=next;
while j<=n do
begin
sieve:=sieve-[j];
j:=j+next;
end;
until sieve=[ ];
j:=0;
for next:=2 to n do
if (next in primes) then
begin
write(next:4);
j:=j+1;
if (j mod 5=0) then writeln;
end;
writeln;
end.
程序运行输出的结果:
2 3 5 7 11
13 17 19 23 29
31 37 41 43 47
53 59 61 67 71
73 79 83 89 97