数据结构学习笔记——线性表的应用
线性表的应用
计算任意两个表的简单自然连接过程讨论线性表的应用。假设有两个表A和B,分别是m1行、n1列和m2行、n2列,它们简单自然连接结果C=A*B(i==j),其中i表示表A中列号,j表示表B中的列号,C为A和B的笛卡儿积中满足指定连接条件的所有记录组,该连接条件为表A的第i列与表B的第j列相等。
如:
1 2 3 3 5
A = 2 3 3 B = 1 6
1 1 1 3 4
1 2 3 3 5
1 2 3 3 4
A*B(3==1) = 2 3 3 3 5
2 3 3 3 4
1 1 1 1 6
数据组织
由于每个表的行数不确定,为此,用单链表作为表的存储结构,每行作为一个数据结点。另外,每行中的数据个数也是不确定的,但由于提供随机查找行中的数据,所以每行的数据采用顺序存储结构,这里用长度为MaxCol的数组存储每行的数据。因此,该单链表中数据结点类型定义如下:
typedef struct Nodel
{
ElemType data[MaxCol];
Struct Nodel *next; //指向后继结点
}DList; //定义数据结点类型
另外,需要指定每个表的行数和列数,为此将单链表的头结点类型定义如下:
{
int Row,Col; /*行数和列数*/
DList *next; /*指向第一个数据结点*/
} HList;
这里的头节点带有附加信息
建立线性表
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
连接表算法
为了实现两个表h1和h2的简单自然连接,先要输入两个表连接的列序号f1和f2,然后扫描单链表h1,对于h1的每个结点,从头至尾扫描单链表h2,若自然连接条件成立,即h1的当前结点*p和h2的当前结点*q满足:
p->data[f1-1]==q->data[f2-1]
则在新建单链表h中添加一个新结点。
新建的单链表h也是采用尾插法建表方法创建的。
2 {
3 int f1,f2,i;DList *p=h1->next,*q,*s,*r;
4 printf("连接字段是:第1个表位序,第2个表位序:");
5 scanf("%d%d",&f1,&f2);
6 h=(HList *)malloc(sizeof(HList));
7 h->Row=0;
8 h->Col=h1->Col+h2->Col;
9 h->next=NULL;
10 while (p!=NULL)
11 {
12 q=h2->next;
13 while (q!=NULL)
14 { if (p->data[f1-1]==q->data[f2-1]) /*对应字段值相等*/
15 { s=(DList *)malloc(sizeof(DList));
16 /*创建一个数据结点*/
17 for (i=0;i<h1->Col;i++) /*复制表1的当前行*/
18 s->data[i]=p->data[i];
19 for (i=0;i<h2->Col;i++)
20 s->data[h1->Col+i]=q->data[i];/*复制表2的当前行*/
21 if (h->next==NULL) h->next=s;
22 else r->next=s;
23 r=s; /*r始终指向最后数据结点*/
24 h->Row++; /*表行数增1*/
25 }
26 q=q->next; /*表2下移一个记录*/
27 }
28 p=p->next; /*表1下移一个记录*/
29 }
30 r->next=NULL;/*表尾结点next域置空*/
31 }
有序表
所谓有序表,是指这样的线性表,其中所有元素以递增或递减方式排列,并规定有序表中不存在元素值相同的元素。在这里仍以顺序表进行存储。
其中只有ListInsert()基本运算与前面的顺序表对应的运算有所差异,其余都是相同的。有序表的ListInsert()运算对应的算法如下:
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
例:设计一个算法将两个有序表合并成一个有序表
例:已知三个带头节点的单链表LA,LB和LC中的结点均依元素自小至大非递增排列(每个链表不存在数值相同的点),但可能存在3个链表中都存在的点。编写一算法做如下操作:
使LA仅留下3个表中均包含的数素元素的结点,并且没有数据值相同的结点,并释放LA中所有无用的结点。要求算法时间复杂度为O(m+n+p),m\n\p分别为3个表的长度。
一元多项式的计算(非求值)
对于一元多项式:
P=p0+p1X+…….+pnXn
在计算机中,可以用一个线性表来表示:
P = (p0, p1, …,pn)
但是对于形如
S(x) = 1 + 3x10000 – 2x20000
的多项式,上述表示方法是否合适?
一般情况下的一元稀疏多项式可写成
Pn(x) = p1xe1 + p2xe2 + ... ... + pmxem
其中:pi 是指数为ei 的项的非零系数,
0≤ e1 < e2 <... ...< em = n
可以下列线性表表示:
((p1, e1), (p2, e2), ┄, (pm,em) )
例如:
P999(x) = 7x3 - 2x12 - 8x999
可用线性表
( (7, 3), (-2, 12), (-8, 999) )
表示
因此数据可组织成:
// 用带表头结点的有序链表表示多项式
结点的数据元素类型定义为:
float coef; // 系数
int expn; // 指数
} term, ElemType;
其应实现的基本运算有:
建立
CreatPolyn ( &P, m )
操作结果:输入 m 项的系数和指数,
建立一元多项式 P。
销毁
DestroyPolyn ( &P )
初始条件:一元多项式 P 已存在。
操作结果:销毁一元多项式 P。
打印
PrintPolyn ( &P )
初始条件:一元多项式 P 已存在。
操作结果:打印输出一元多项式 P。
项数
PolynLength( P )
初始条件:一元多项式 P 已存在。
操作结果:返回一元多项式 P 中的项数。
相加
AddPolyn ( &Pa, &Pb )
初始条件:一元多项式 Pa 和 Pb 已存在。
操作结果:完成多项式相加运算,即:
Pa = Pa+Pb,并销毁一元多项式 Pb。
相减
SubtractPolyn ( &Pa, &Pb )
初始条件:一元多项式 Pa 和 Pb 已存在。
操作结果:完成多项式相减运算,即:
Pa = Pa-Pb,并销毁一元多项式 Pb。
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
约瑟夫环问题
约瑟夫环(Josephus)问题是由古罗马的史学家约瑟夫(Josephus)提出的,他参加并记录了公元66—70年犹太人反抗罗马的起义。约瑟夫作为一个将军,设法守住了裘达伯特城达47天之久,在城市沦陷之后,他和40名死硬的将士在附近的一个洞穴中避难。在那里,这些叛乱者表决说“要投降毋宁死”。于是,约瑟夫建议每个人轮流杀死他旁边的人,而这个顺序是由抽签决定的。约瑟夫有预谋地抓到了最后一签,并且,作为洞穴中的两个幸存者之一,他说服了他原先的牺牲品一起投降了罗马。
约瑟夫环问题的具体描述是:设有编号为1,2,……,n的n(n>0)个人围成一个圈,从第1个人开始报数,报到m时停止报数,报m的人出圈,再从他的下一个人起重新报数,报到m时停止报数,报m的出圈,……,如此下去,直到所有人全部出圈为止。当任意给定n和m后,设计算法求n个人出圈的次序。
解决方法:建立无头节点的循环链表(有头节点亦可)
另一种方法(数学方法)时间复杂度o(n)[转]
无论是用链表实现还是用数组实现都有一个共同点:要模拟整个游戏过程,不仅程序写起来比较烦,而且时间复杂
度高达O(nm),当n,m非常大(例如上百万,上千万)的时候,几乎是没有办法在短时间内出结果的。
为了讨论方便,先把问题稍微改变一下,并不影响原意:
问题描述:n个人(编号0~(n-1)),从0开始报数,报到(m-1)的退出,剩下的人继续从0开始报数。求胜利者的编号
。
我们知道第一个人(编号一定是m%n-1) 出列之后,剩下的n-1个人组成了一个新的约瑟夫环(以编号为k=m%n的人开
始):
k k+1 k+2 ... n-2, n-1, 0, 1, 2, ... k-2
并且从k开始报0。
现在我们把他们的编号做一下转换:
k --> 0
k+1 --> 1
k+2 --> 2
...
...
k-2 --> n-2
k-1 --> n-1
变换后就完完全全成为了(n-1)个人报数的子问题,假如我们知道这个子问题的解:例如x是最终的胜利者,那么根
据上面这个表把这个x变回去不刚好就是n个人情况的解吗?!!变回去的公式很简单,相信大家都可以推出来:x'
=(x+k)%n
如何知道(n-1)个人报数的问题的解?对,只要知道(n-2)个人的解就行了。(n-2)个人的解呢?当然是先求(n-3)的
情况 ---- 这显然就是一个倒推问题!好了,思路出来了,下面写递推公式:
令f[i]表示i个人玩游戏报m退出最后胜利者的编号,最后的结果自然是f[n]
递推公式
f[1]=0;
f[i]=(f[i-1]+m)%i; (i>1)
有了这个公式,我们要做的就是从1-n顺序算出f[i]的数值,最后结果是f[n]。因为实际生活中编号总是从1开始,
我们输出f[n]+1
由于是逐级递推,不需要保存每个f[i],程序也是异常简单:
main()
{
int n, m, i, s=0;
printf ("N M = "); scanf("%d%d", &n, &m);
for (i=2; i<=n; i++) s=(s+m)%i;
printf ("The winner is %d\n", s+1);
}
这个算法的时间复杂度为O(n),相对于模拟算法已经有了很大的提高。算n,m等于一百万,一千万的情况不是问题了。
posted on 2009-09-17 14:11 ONLY LOVE PROGRAME 阅读(2760) 评论(0) 编辑 收藏 举报