数据结构学习笔记——线性表的应用

线性表的应用


 




线性表的自然连接


计算任意两个表的简单自然连接过程讨论线性表的应用。假设有两个表AB,分别是m1行、n1列和m2行、n2,它们简单自然连接结果C=A*Bi==j,其中i表示表A中列号,j表示表B中的列号,CAB的笛卡儿积中满足指定连接条件的所有记录组,该连接条件为表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的数组存储每行的数据。因此,该单链表中数据结点类型定义如下:

#indefine MaxCol 10                 //最大列数
typedef struct Nodel
{
   ElemType data[MaxCol];
   Struct Nodel 
*next;                 //指向后继结点
}DList;                                //定义数据结点类型

 

另外,需要指定每个表的行数和列数,为此将单链表的头结点类型定义如下:

typedef struct Node2   /*定义头结点类型*/

  
int Row,Col;         /*行数和列数*/
  DList 
*next;         /*指向第一个数据结点*/
} HList;

这里的头节点带有附加信息

 

建立线性表

Code

 

连接表算法

为了实现两个表h1h2的简单自然连接,先要输入两个表连接的列序号f1f2,然后扫描单链表h1,对于h1的每个结点,从头至尾扫描单链表h2,若自然连接条件成立,h1的当前结点*ph2的当前结点*q满足:

         p->data[f1-1]==q->data[f2-1]

则在新建单链表h中添加一个新结点。

      新建的单链表h也是采用尾插法建表方法创建的。

     

 1 void link(HList *h1,HList *h2,HList *&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()运算对应的算法如下:

  

Code

例:设计一个算法将两个有序表合并成一个有序表

例:已知三个带头节点的单链表LALBLC中的结点均依元素自小至大非递增排列(每个链表不存在数值相同的点),但可能存在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) )  

表示

 

因此数据可组织成:

typedef  OrderedLinkList   pol;
// 用带表头结点的有序链表表示多项式

结点的数据元素类型定义为:

typedef struct {      // 项的表示
    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 = PaPb,并销毁一元多项式 Pb

 

相减

SubtractPolyn ( &Pa, &Pb )

初始条件:一元多项式 Pa Pb 已存在。

操作结果:完成多项式相减运算,即:

            Pa = Pa-Pb,并销毁一元多项式 Pb

 

Code


Code


Code






 

约瑟夫环问题

  约瑟夫环(Josephus)问题是由古罗马的史学家约瑟夫(Josephus)提出的,他参加并记录了公元66—70年犹太人反抗罗马的起义。约瑟夫作为一个将军,设法守住了裘达伯特城达47天之久,在城市沦陷之后,他和40名死硬的将士在附近的一个洞穴中避难。在那里,这些叛乱者表决说要投降毋宁死。于是,约瑟夫建议每个人轮流杀死他旁边的人,而这个顺序是由抽签决定的。约瑟夫有预谋地抓到了最后一签,并且,作为洞穴中的两个幸存者之一,他说服了他原先的牺牲品一起投降了罗马。
  约瑟夫环问题的具体描述是:设有编号为12……nn(n>0)个人围成一个圈,从第1个人开始报数,报到m时停止报数,报m的人出圈,再从他的下一个人起重新报数,报到m时停止报数,报m的出圈,……,如此下去,直到所有人全部出圈为止。当任意给定nm后,设计算法求n个人出圈的次序。 

 

解决方法:建立无头节点的循环链表(有头节点亦可)

 

另一种方法(数学方法)时间复杂度o(n)[转]


无论是用链表实现还是用数组实现都有一个共同点:要模拟整个游戏过程,不仅程序写起来比较烦,而且时间复杂
度高达O(nm),当nm非常大(例如上百万,上千万)的时候,几乎是没有办法在短时间内出结果的。

为了讨论方便,先把问题稍微改变一下,并不影响原意:

问题描述: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],程序也是异常简单:

#include <stdio.h>
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),相对于模拟算法已经有了很大的提高。算nm等于一百万,一千万的情况不是问题了。
====================================================================================================================
另外要说明的是,这个方法只能在o(n)的方法内求出第任意某个出去的人的位置。如果要求出整个出去序列,用这个方法还是o(n^2)的。。求所以出去序列,用线段数可以做到o(nlog(n))..ms没有比这个快的了。。

原文地址:https://www.cnblogs.com/_programmer/p/1568481.html