`

黄刘生--数据结构--答案

阅读更多
第一章 绪论
1.1 简述下列概念:数据、数据元素、数据类型、数据结构、逻辑结构、存储结构、线性结构、非线性结构。
● 数据:指能够被计算机识别、存储和加工处理的信息载体。
● 数据元素:就是数据的基本单位,在某些情况下,数据元素也称为元素、结点、顶点、记录。数据元素有时可以由若干数据项组成。
● 数据类型:是一个值的集合以及在这些值上定义的一组操作的总称。通常数据类型可以看作是程序设计语言中已实现的数据结构。
● 数据结构:指的是数据之间的相互关系,即数据的组织形式。一般包括三个方面的内容:数据的逻辑结构、存储结构和数据的运算。
● 逻辑结构:指数据元素之间的逻辑关系。
● 存储结构:数据元素及其关系在计算机存储器内的表示,称为数据的存储结构.
● 线性结构:数据逻辑结构中的一类。它的特征是若结构为非空集,则该结构有且只有一个开始结点和一个终端结点,并且所有结点都有且只有一个直接前趋和一个直接后继。线性表就是一个典型的线性结构。栈、队列、串等都是线性结构。
● 非线性结构:数据逻辑结构中的另一大类,它的逻辑特征是一个结点可能有多个直接前趋和直接后继。数组、广义表、树和图等数据结构都是非线性结构。 
1.2 试举一个数据结构的例子、叙述其逻辑结构、存储结构、运算三个方面的内容。
答:
   例如有一张学生体检情况登记表,记录了一个班的学生的身高、体重等各项体检信息。这张登记表中,每个学生的各项体检信息排在一行上。这个表就是一个数据结构。每个记录(有姓名,学号,身高和体重等字段)就是一个结点,对于整个表来说,只有一个开始结点(它的前面无记录)和一个终端结点(它的后面无记录),其他的结点则各有一个也只有一个直接前趋和直接后继(它的前面和后面均有且只有一个记录)。这几个关系就确定了这个表的逻辑结构是线性结构。
  这个表中的数据如何存储到计算机里,并且如何表示数据元素之间的关系呢? 即用一片连续的内存单元来存放这些记录(如用数组表示)还是随机存放各结点数据再用指针进行链接呢? 这就是存储结构的问题。
  在这个表的某种存储结构基础上,可实现对这张表中的记录进行查询,修改,删除等操作。对这个表可以进行哪些操作以及如何实现这些操作就是数据的运算问题了。
1.3 常用的存储表示方法有哪几种?
答:
  常用的存储表示方法有四种:
 ● 顺序存储方法:它是把逻辑上相邻的结点存储在物理位置相邻的存储单元里,结点间的逻辑关系由存储单元的邻接关系来体现。由此得到的存储表示称为顺序存储结构,通常借助程序语言的数组描述。
 ● 链接存储方法:它不要求逻辑上相邻的结点在物理位置上亦相邻,结点间的逻辑关系是由附加的指针字段表示。由此得到的存储表示称为链式存储结构,通常借助于程序语言的指针类型描述。
 ● 索引存储方法:除建立存储结点信息外,还建立附加的索引表来标识结点的地址。组成索引表的索引项由结点的关键字和地址组成。若每个结点在索引表中都有一个索引项,则该索引表称之为稠密索引(Dense Index)。若一组结点在索引表中只对应一个索引项,则该索引表称为稀疏索引。
 ● 散列存储方法:就是根据结点的关键字直接计算出该结点的存储地址
1.4 设三个函数f,g,h分别为 f(n)=100n3+n2+1000 , g(n)=25n3+5000n2 , h(n)=n1.5+5000nlgn 请判断下列关系是否成立:
(1) f(n)=O(g(n)) 
(2) g(n)=O(f(n)) 
(3) h(n)=O(n1.5)
(4) h(n)=O(nlgn)
分析:
  数学符号"O"的严格的数学定义:
若T(n)和f(n)是定义在正整数集合上的两个函数,则T(n)=O(f(n))表示存在正的常数C和n0,使得当n≥n0时都满足0≤T(n)≤C?f(n)。
  通俗地说,就是当n→∞时,f(n)的函数值增长速度与T(n)的增长速度同阶。一般,一个函数的增长速度与该函数的最高次阶同阶。
即:
   O(f(n))=n3
   O(g(n))=n3
   O(h(n))=n1.5
  所以答案为:
答:
   ●(1)成立。 
   ●(2)成立。
   ●(3)成立。
   ●(4)不成立。
1.5 设有两个算法在同一机器上运行,其执行时间分别为100n2和2n,要使前者快于后者,n至少要多大?
分析:
  要使前者快于后者,即前者的时间消耗低于后者,即:
    100n2<2n
  求解上式,可得
答:
  n=15
1.6 设n为正整数,利用大"O"记号,将下列程序段的执行时间表示为n的函数。
(1) i=1; k=0; 
  while(i<n)
   { k=k+10*i;i++;
   } 
分析:
  i=1; //1
  k=0; //1
   while(i<n) //n
   { k=k+10*i; //n-1
    i++; //n-1
   } 
由以上列出的各语句的频度,可得该程序段的时间消耗:
   T(n)=1+1+n+(n-1)+(n-1)=3n
可表示为T(n)=O(n)
(2) i=0; k=0;
  do{
    k=k+10*i; i++; 
   }
  while(i<n); 
分析:
  i=0; //1
  k=0; //1
  do{ //n
    k=k+10*i; //n
    i++; //n
   }
  while(i<n);//n 
由以上列出的各语句的频度,可得该程序段的时间消耗:
  T(n)=1+1+n+n+n+n=4n+2
可表示为T(n)=O(n)
(3) i=1; j=0; 
  while(i+j<=n) 
   {
    if (i>j) j++;
    else i++;
   }
分析:
  通过分析以上程序段,可将i+j看成一个控制循环次数的变量,且每执行一次循环,i+j的值加1。该程序段的主要时间消耗是while循环,而while循环共做了n次,所以该程序段的执行时间为:
    T(n)=O(n)
(4)x=n; // n>1 
 while (x>=(y+1)*(y+1))
  y++;
分析:
  由x=n且x的值在程序中不变,又while的循环条件(x>=(y+1)*(y+1))可知:当(y+1)*(y+1)刚超过n的值时退出循环。
  由(y+1)*(y+1)<n得:y<n^0.5-1
  所以,该程序段的执行时间为:
   向下取整(n^0.5-1)
(5) x=91; y=100; 
    while(y>0)
    if(x>100)
     {x=x-10;y--;}
    else x++;
分析:
  x=91; //1
  y=100; //1
  while(y>0) //1101
    if(x>100) //1100
     { x=x-10; //100
      y--; //100
     }
    else 
     x++; //1000
  以上程序段右侧列出了执行次数。该程序段的执行时间为:
      T(n)=O(1)
1.7 算法的时间复杂度仅与问题的规模相关吗?
答:
  算法的时间复杂度不仅与问题的规模相关,还与输入实例中的初始状态有关。但在最坏的情况下,其时间复杂度就是只与求解问题的规模相关的。我们在讨论时间复杂度时,一般就是以最坏情况下的时间复杂度为准的。
1.8 按增长率由小至大的顺序排列下列各函数:
  2100, (3/2)n,(2/3)n, nn ,n0.5 , n! ,2n ,lgn ,nlgn, n(3/2) 
答:
  常见的时间复杂度按数量级递增排列,依次为:常数阶0(1)、对数阶0(log2n)、线性阶0(n)、线性对数阶0(nlog2n)、平方阶0(n2)、立方阶0(n3)、k次方阶0(nk)、指数阶0(2n)。
先将题中的函数分成如下几类:
常数阶:2100
对数阶:lgn
K次方阶:n0.5、n(3/2)
指数阶 (按指数由小到大排):nlgn、(3/2)n、2n、 n!、 nn
注意:(2/3)^n由于底数小于1,所以是一个递减函数,其数量级应小于常数阶。 
根据以上分析按增长率由小至大的顺序可排列如下:
(2/3)n < 2100 < lgn < n0.5 < n(3/2) < nlgn < (3/2)n < 2n < n! < nn  
1.9 有时为了比较两个同数量级算法的优劣,须突出主项的常数因子,而将低次项用大"O"记号表示。例如,设T1(n)=1.39nlgn+100n+256=1.39nlgn+O(n), T2(n)=2.0nlgn-2n=2.0lgn+O(n), 这两个式子表示,当n足够大时T1(n)优于T2(n),因为前者的常数因子小于后者。请用此方法表示下列函数,并指出当n足够大时,哪一个较优,哪一个较劣?
    函数        大"O"表示    优劣 
(1) T1(n)=5n2-3n+60lgn     5n2+O(n)     较差 
(2) T2(n)=3n2+1000n+3lgn    3n2+O(n)     其次 
(3) T3(n)=8n2+3lgn       8n2+O(lgn)   最差 
(4) T4(n)=1.5n2+6000nlgn   1.5n2+O(nlgn)   最优
第二章 线性表
 
2.6 下述算法的功能是什么?
  LinkList Demo(LinkList L){ // L 是无头结点单链表
   ListNode *Q,*P;
   if(L&&L->next){
    Q=L;L=L->next;P=L;
    while (P->next) P=P->next;
     P->next=Q; Q->next=NULL;
    }
    return L;
  }// Demo
答:
  该算法的功能是:将开始结点摘下链接到终端结点之后成为新的终端结点,而原来的第二个结点成为新的开始结点,返回新链表的头指针。
关闭
 
2.7 设线性表的n个结点定义为(a0,a1,...an-1),重写顺序表上实现的插入和删除算法:InsertList 和DeleteList.
解:算法如下:
#define ListSize 100 // 假定表空间大小为100
typedef int DataType;//假定DataType的类型为int型
typedef struct{
DataType data[ListSize];// 向量data用于存放表结点
int length; // 当前的表长度
} Seqlist;
//以上为定义表结构
void InsertList ( Seqlist *L, Datatype x, int i)
{
//将新结点x插入L所指的顺序表的第i个结点ai的位置上,即插入的合法位置为:0<=i<=L->length
int j;
if ( i < 0 || i > L -> length )
Error("position error");// 非法位置,退出,该函数定义见教材P7.
if ( L->length>=ListSize )
Error(“overflow");
for ( j=L->length-1 ; j >= i ; j --)
L->data[ j+1]=L->data [ j ];
L->data[ i ]=x ;
L->length++ ;
}
void DeleteList ( Seqlist *L, int i )
{// 从L所指的顺序表中删除第i个结点ai,合法的删除位置为0<=i<=L->length-1
int j;
if ( i< 0 || i >= L-> length)
Error( " position error" ) ;
for ( j = i ; j < L-> length ; j++ )
L->data [ j ]=L->data [ j+1]; //结点前移
L-> length-- ; //表长减小
}
关闭
 
2.8 试分别用顺序表和单链表作为存储结构,实现将线性表(a0,a1,...an-1)就地逆置的操作,所谓"就地"指辅助空间应为O(1)。
答:
1. 顺序表:
  要将该表逆置,可以将表中的开始结点与终端结点互换,第二个结点与倒数第二个结点互换,如此反复,就可将整个表逆置了。算法如下: 
// 顺序表结构定义同上题
 void ReverseList( Seqlist *L)
  {
   DataType temp ; //设置临时空间用于存放data
   int i;
   for (i=0;i<=L->length/2;i++)//L->length/2为整除运算
    { temp = L->data; //交换数据
     L -> data[ i ] = L -> data[ L -> length-1-i];
     L -> data[ L -> length - 1 - i ] = temp;
    }
  }
2. 链表:
  分析:
  可以用交换数据的方式来达到逆置的目的。但是由于是单链表,数据的存取不是随机的,因此算法效率太低。可以利用指针改指来达到表逆置的目的。具体情况入下:
  (1)当链表为空表或只有一个结点时,该链表的逆置链表与原表相同。
  (2)当链表含2个以上结点时,可将该链表处理成只含第一结点的带头结点链表和一个无头结点的包含该链表剩余结点的链表。然后,将该无头结点链表中的所有结点顺着链表指针,由前往后将每个结点依次从无头结点链表中摘下,作为第一个结点插入到带头结点链表中。这样就可以得到逆置的链表。算法是这样的:
  结点结构定义如下:
    typedef char DataType; //假设结点的数据域类型的字符
    typedef struct node{ //结点类型定义
      DataType data; //结点的数据域
      struct node *next;//结点的指针域
     }ListNode;
    typedef ListNode *LinkList;
    ListNode *p;
    LinkList head;
  LinkList ReverseList( LinkList head )
   {// 将head 所指的单链表(带头结点)逆置
    ListNode *p ,*q ;//设置两个临时指针变量
if( head->next && head->next->next)
     { //当链表不是空表或单结点时
      p=head->next;
      q=p->next;
      p -> next=NULL; //将开始结点变成终端结点
      while (q)
       { //每次循环将后一个结点变成开始结点 
        p=q; 
        q=q->next ;
        p->next = head-> next ;
        head->next = p;
       }
      return head;
     }
    return head; //如是空表或单结点表,直接返回head
   }
关闭
 
2.9 设顺序表L是一个递增有序表,试写一算法,将x插入L中,并使L仍是一个有序表。
答:
  因已知顺序表L是递增有序表,所以只要从顺序表终端结点(设为i位置元素)开始向前寻找到第一个小于或等于x的元素位置i后插入该位置即可。
  在寻找过程中,由于大于x的元素都应放在x之后,所以可边寻找,边后移元素,当找到第一个小于或等于x的元素位置i时,该位置也空出来了。
  算法如下:
   //顺序表存储结构如题2.7
    void InsertIncreaseList( Seqlist *L , Datatype x )
     { 
      int i;
      if ( L->length>=ListSize)
       Error(“overflow");
      for ( i=L -> length ; i>0 && L->data[ i-1 ] > x ; i--)
       L->data[ i ]=L->data[ i ] ; // 比较并移动元素
      L->data[ i ] =x;
      L -> length++;
     }
关闭
 
2.10 设顺序表L是一个递减有序表,试写一算法,将x插入其后仍保持L的有序性。
答:
  与上题相类似,只要从终端结点开始往前找到第一个比x大(或相等)的结点数据,在这个位置插入就可以了。(边寻找,边移动)算法如下:
  void InsertDecreaseList( Seqlist *L, Datatype x )
   {
    int i;
    if ( L->length>=ListSize)
     Error(“overflow");
    for ( i=L -> length ; i>0 && L->data[ i-1 ] < x ; i--)
     L->data[ i ]=L->data[ i ] ; // 比较并移动元素
    L->data[ i ] =x;
    L -> length++;
   }
关闭
 
2.11 写一算法在单链表上实现线性表的ListLength(L)运算。
解:
  由于在单链表中只给出一个头指针,所以只能用遍历的方法来数单链表中的结点个数了。算法如下:
  int ListLength ( LinkList L )
   {
    int len=0 ;
    ListNode *p;
    p=L; //设该表有头结点
    while ( p->next )
     {
      p=p->next;
      len++;
     }
    return len;
   }
关闭
 
2.12 已知L1和L2分别指向两个单链表的头结点,且已知其长度分别为m和n。试写一算法将这两个链表连接在一起,请分析你的算法的时间复杂度。
解:
  分析:
  由于要进行的是两单链表的连接,所以应找到放在前面的那张表的表尾结点,再将后表的开始结点链接到前表的终端结点后即可。该算法的主要时间消耗是用在寻找第一张表的终端尾结点上。这两张单链表的连接顺序无要求,并且已知两表的表长,则为了提高算法效率,可选表长小的单链表在前的方式连接。
  具体算法如下:
  LinkList Link( LinkList L1 , LinkList L2,int m,int n )
   {//将两个单链表连接在一起
    ListNode *p , *q, *s ;
    //s指向短表的头结点,q指向长表的开始结点,回收长表头结点空间 
    if (m<=n)
     {s=L1;q=L2->next;free(L2);}
    else {s=L2;q=L1->next;free(L1);}
    p=s;
    while ( p->next ) p=p->next; //查找短表终端结点
p->next = q; //将长表的开始结点链接在短表终端结点后
    return s;
   }
  本算法的主要操作时间花费在查找短表的终端结点上,所以本算的法时间复杂度为:
     O(min(m,n))
关闭
 
2.13 设 A和B是两个单链表,其表中元素递增有序。试写一算法将A和B归并成一个按元素值递减有序的单链表C,并要求辅助空间为O(1),请分析算法的时间复杂度。
解:
  根据已知条件,A和B是两个递增有序表,所以可以先取A表的表头建立空的C表。然后同时扫描A表和B表,将两表中最大的结点从对应表中摘下,并作为开始结点插入C表中。如此反复,直到A表或B表为空。最后将不为空的A表或B表中的结点依次摘下并作为开始结点插入C表中。这时,得到的C表就是由A表和B表归并成的一个按元素值递减有序的单链表C。并且辅助空间为O(1)。
  算法如下:
  LinkList MergeSort ( LinkList A , LinkList B )
   {// 归并两个带头结点的递增有序表为一个带头结点递减有序表
    ListNode *pa , *pb , *q , *C ;
    pa=A->next;//pa指向A表开始结点
    C=A;C->next=NULL;//取A表的表头建立空的C表
    pb=B->next;//pb指向B表开始结点
    free(B);//回收B表的头结点空间
    while ( pa && pb)
     {
      if ( pb->data <= pa->data )
       { // 当B中的元素小于等于A中当前元素时,将pa表的开始结点摘下
        q=pa;pa=pa->next;
       }
      else
       {// 当B中的元素大于A中当前元素时,将pb表的开始结点摘下
        q=pb;pb=pb->next;}
      q->next=C->next;C->next=q;//将摘下的结点q作为开始结点插入C表
     }
    //若pa表非空,则处理pa表
    while(pa){
      q=pa;pa=pa->next;
      q->next=C->next;C->next=q;}
    //若pb表非空,则处理pb表
    while(pb){
      q=pb;pa=pb->next;
      q->next=C->next;C->next=q;}
    return(C);
   } 
  该算法的时间复杂度分析如下:
  算法中有三个while 循环,其中第二个和第三个循环只执行一个。每个循环做的工作都是对链表中结点扫描处理。整个算法完成后,A表和B表中的每个结点都被处理了一遍。所以若A表和B表的表长分别是m和n,则该算法的时间复杂度O(m+n)
关闭
 
2.14 已知单链表L是一个递增有序表,试写一高效算法,删除表中值大于min 且小于max的结点(若表中有这样的结点),同时释放被删结点的空间,这里min 和 max是两个给定的参数。请分析你的算法的时间复杂度。
解:
  要解这样的问题,我们首先想到的是拿链表中的元素一个个地与max和min比较,然后删除这个结点。由于为已知其是有序链表,则介于min 和max之间的结点必为连续的一段元素序列。所以我们只要先找到所有大于min结点中的最小结点的直接前趋结点*p后,依次删除小于max的结点,直到第一个大于等于max结点*q位置,然后将*p结点的直接后继指针指向*q结点。 
  算法如下:
  void DeleteList ( LinkList L, DataType min , DataType max )
   {
    ListNode *p , *q , *s;
    p=L;
    while( p->next && p->next->data <=min ) 
    //找比min大的前一个元素位置
     p=p->next;
    q=p->next;//p指向第一个不大于min结点的直接前趋,q指向第一个大于min的结点
    while(q &&q->data<max)
     {s=q;q=q->next;
      free(s);//删除结点,释放空间
     }
    p->next=q;//将*p结点的直接后继指针指向*q结点
   }
关闭
 
2.15 写一算法将单链表中值重复的结点删除,使所得的结果表中各结点值均不相同
解:
  本题可以这样考虑,先取开始结点中的值,将它与其后的所有结点值一一比较,发现相同的就删除掉,然后再取第二结点的值,重复上述过程直到最后一个结点。
  具体算法:
   void DeleteList ( LinkList L )
    { 
     ListNode *p , *q , *s;
     p=L-next;
     while( p->next&&p->next->next)
      {
       q=p;//由于要做删除操作,所以q指针指向要删除元素的直接前趋
       while (q->next)
        if (p->data==q->next->data)
         {s=q->next;q->next=s->next;free(s);//删除与*p的值相同的结点
         }
        else q=q->next;
       p=p->next;
      }
    }  
关闭
 
2.16 假设在长度大于1的单循环链表中,既无头结点也无头指针。s为指向链表中某个结点的指针,试编写算法删除结点*s的直接前趋结点。
解:
  已知指向这个结点的指针是*s,那么要删除这个结点的直接前趋结点,就只要找到一个结点,它的指针域是指向*s的直接前趋,然后用后删结点法,将结点*s的直接前趋结点删除即可。
  算法如下:
   void DeleteNode( ListNode *s)
    {//删除单循环链表中指定结点的直接前趋结点
      ListNode *p, *q;
      p=s;
      while( p->next->next!=s) 
       p=p->next;
      //删除结点
      q=p->next;
      p->next=q->next;
      free(p); //释放空间
    }
  注意:
  若单循环链表的长度等于1,则只要把表删空即可。
关闭
 
2.17 已知由单链表表示的线性表中,含有三类字符的数据元素(如:字母字符、数字字符和其它字符),试编写算法构造三个以循环链表表示的线性表,使每个表中只含同一类的字符,且利用原表中的结点空间作为这三个表的结点空间,头结点可另辟空间。
解:
  要解决这样的问题,只要新建三个头结点,然后在原来的单链表中依次查询,找到一类字符结点时,就摘下此结点链接到相应头结点指明的新链表中就是了。
算法如下:
  //设已建立三个带头结点的空循环链表A,B,C且A、B、C分别是尾指针.
  void DivideList( LinkList L, LinkList A, LinkList B, LinkList C)
   {
    ListNode *p=L->next, *q;
    while ( p )
     {
      if ( p->data>='a' &&p->data<='z'|| p->data>='A' &&p->data<='Z')
       {
        q=p->next; 
        p=p->next;//指向下一结点
        q->next=A->next;//将字母结点链到A表中
        A->next=q;A=q;
       }
      else if( p->data>='0' && p->data<='9')
         { // 分出数字结点
          q=p->next; 
          p=p->next;//指向下一结点
          q->next=B->next;//将数字结点链到B表中
          B->next=q;B=q;
         }
        else { //分出其他字符结点
            q=p->next; 
            p=p->next;//指向下一结点
            q->next=C->next;//将其他结点链到C表中
            C->next=q;C=q;
           }
     }
   }//结束

关闭
 
2.18 设有一个双链表,每个结点中除有prior、data和next三个域外,还有一个访问频度域freq,在链表被起用之前,其值均初始化为零。每当在链表进行一次LocateNode(L,x)运算时,令元素值为x的结点中freq域的值加1,并调整表中结点的次序,使其按访问频度的递减序排列,以便使频繁访问的结点总是靠近表头。试写一符合上述要求的LocateNode运算的算法。 
解:
  LocateNode运算的基本思想就是在双向链表中查找值为x的结点,具体方法与单链表中查找一样。找到结点*p后给freq域的值加1。由于原来比*p结点查找频度高的结点都排它前面,所以,接下去要顺着前趋指针找到第一个频度小于或等于*p结点频度的结点*q后,将*p结点从原来的位置删除,并插入到*q后就可以了。
算法如下:
 //双向链表的存储结构
   typedef struct dlistnode{
     DataType data;
     struct dlistnode *prior,*next;
     int freq;
    }DListNode;
   typedef DListNode *DLinkList;
   void LocateNode( LinkList L, DataType x)
    {
     ListNode *p, *q;
     p=L->next; //带有头结点
     while( p&&p->data!=x )
      p=p->next;
     if (!p) ERROR("x is not in L");//双链表中无值为x的结点
     else { p->freq++;//freq加1
        q=p->prior;//以q为扫描指针寻找第一个频度大于或等于*p频度的结点
        while(q!=L&&q->freq<p->freq)
         q=q->prior;
        if (q->next!=p)//若* q结点和*p结点不为直接前趋直接后继关系,
                //则将*p结点链到* q结点后
         {p->prior->next=p->next;//将*p从原来位置摘下
          p->next->prior=p->prior;
          q->next->prior=p;//将*p插入*q之后。
          p->next=q->next;
          q->next=p;
          p->prior=q;
         }
        }
    }
第三章 栈和队列
3.1 设将整数1,2,3,4依次进栈,但只要出栈时栈非空,则可将出栈操作按任何次序夹入其中,请回答下述问题: 
  (1)若入、出栈次序为Push(1), Pop(),Push(2),Push(3), Pop(), Pop( ),Push(4), Pop( ),则出栈的数字序列为何(这里Push(i)表示i进栈,Pop( )表示出栈)? 
  (2)能否得到出栈序列1423和1432?并说明为什么不能得到或者如何得到。 
  (3)请分析 1,2 ,3 ,4 的24种排列中,哪些序列是可以通过相应的入出栈操作得到的。 
答:
  (1)出栈序列为:1324
(2)不能得到1423序列。因为要得到14的出栈序列,则应做Push(1),Pop(),Push(2),Push (3),Push(4),Pop()。这样,3在栈顶,2在栈底,所以不能得到23的出栈序列。能得到1432的出栈序列。具体操作为:Push(1), Pop(),Push(2),Push(3),Push(4),Pop(),Pop(),Pop()。
  (3)在1,2 ,3 ,4 的24种排列中,可通过相应入出栈操作得到的序列是:
   1234,1243,1324,1342,1432,2134,2143,2314,2341,2431,3214,3241,3421,4321
不能得到的序列是:
    1423,2413,3124,3142,3412,4123,4132,4213,4231,4312
关闭
 
3.2 链栈中为何不设置头结点?
答:
链栈不需要在头部附加头结点,因为栈都是在头部进行操作的,如果加了头结点,等于要对头结点之后的结点进行操作,反而使算法更复杂,所以只要有链表的头指针就可以了。
关闭
 
3.3 循环队列的优点是什么? 如何判别它的空和满? 
答:
  循环队列的优点是:它可以克服顺序队列的"假上溢"现象,能够使存储队列的向量空间得到充分的利用。判别循环队列的"空"或"满"不能以头尾指针是否相等来确定,一般是通过以下几种方法:一是另设一布尔变量来区别队列的空和满。二是少用一个元素的空间,每次入队前测试入队后头尾指针是否会重合,如果会重合就认为队列已满。三是设置一计数器记录队列中元素总数,不仅可判别空或满,还可以得到队列中元素的个数。
关闭
 
3.4 设长度为n的链队用单循环链表表示,若设头指针,则入队出队操作的时间为何? 若只设尾指针呢? 
答:
  当只设头指针时,出队的时间为1,而入队的时间需要n,因为每次入队均需从头指针开始查找,找到最后一个元素时方可进行入队操作。若只设尾指针,则出入队时间均为1。因为是循环链表,尾指针所指的下一个元素就是头指针所指元素,所以出队时不需要遍历整个队列。
关闭
 
3.5 指出下述程序段的功能是什么? 
(1) void Demo1(SeqStack *S){
    int i; arr[64] ; n=0 ;
    while ( StackEmpty(S)) arr[n++]=Pop(S);
    for (i=0, i< n; i++) Push(S, arr);
   } //Demo1
(2) SeqStack S1, S2, tmp;
  DataType x;
  ...//假设栈tmp和S2已做过初始化
  while ( ! StackEmpty (&S1))
   {
    x=Pop(&S1) ;
    Push(&tmp,x);
   }
  while ( ! StackEmpty (&tmp) )
   {
    x=Pop( &tmp); 
    Push( &S1,x);
    Push( &S2, x);
   }
(3) void Demo2( SeqStack *S, int m) 
   { // 设DataType 为int 型
    SeqStack T; int i;
    InitStack (&T);
    while (! StackEmpty( S))
     if(( i=Pop(S)) !=m) Push( &T,i);
    while (! StackEmpty( &T))
     {
      i=Pop(&T); Push(S,i);
     }
   }
(4)void Demo3( CirQueue *Q)
   { // 设DataType 为int 型
    int x; SeqStack S;
    InitStack( &S);
    while (! QueueEmpty( Q ))
     {x=DeQueue( Q); Push( &S,x);}
    while (! StackEmpty( &s))
     { x=Pop(&S); EnQueue( Q,x );}
   }// Demo3
(5) CirQueue Q1, Q2; // 设DataType 为int 型
int x, i , n= 0;
  ... // 设Q1已有内容, Q2已初始化过
  while ( ! QueueEmpty( &Q1) ) 
   { x=DeQueue( &Q1 ) ; EnQueue(&Q2, x); n++;}
  for (i=0; i< n; i++) 
   { x=DeQueue(&Q2) ; 
  EnQueue( &Q1, x) ; EnQueue( &Q2, x);} 

答:
  (1)程序段的功能是将一栈中的元素按反序重新排列,也就是原来在栈顶的元素放到栈底,栈底的元素放到栈顶。此栈中元素个数限制在64个以内。
  (2)程序段的功能是利用tmp栈将一个非空栈s1的所有元素按原样复制到一个栈s2当中去。
  (3)程序段的功能是利用栈T,将一个非空栈S中值等于m的元素全部删去。
  (4)程序段的功能是将一个循环队列Q经过S栈的处理,反向排列,原来的队头变成队尾,原来的队尾变成队头。
  (5)这段程序的功能是将队列1的所有元素复制到队列2中去,但其执行过程是先把队列1的元素全部出队,进入队列2,然后再把队列2的元素复制到队列1中。
关闭
3.6 回文是指正读反读均相同的字符序列,如"abba"和"abdba"均是回文,但"good"不是回文。试写一个算法判定给定的字符向量是否为回文。(提示:将一半字符入栈) 
解:
  根据提示,算法可设计为:
 //以下为顺序栈的存储结构定义
 #define StackSize 100 //假定预分配的栈空间最多为100个元素
 typedef char DataType;//假定栈元素的数据类型为字符
 typedef struct{
  DataType data[StackSize];
  int top;
 }SeqStack; 
 int IsHuiwen( char *t)
  {//判断t字符向量是否为回文,若是,返回1,否则返回0
   SeqStack s;
   int i , len;
   char temp;
   InitStack( &s);
   len=strlen(t); //求向量长度
   for ( i=0; i<len/2; i++)//将一半字符入栈
    Push( &s, t);
   while( !EmptyStack( &s))
    {// 每弹出一个字符与相应字符比较
     temp=Pop (&s);
     if( temp!=S) return 0 ;// 不等则返回0
     else i++;
    } 
   return 1 ; // 比较完毕均相等则返回 1
  }
关闭
 
3.7 利用栈的基本操作,写一个将栈S中所有结点均删去的算法void ClearStack( SeqStack *S),并说明S为何要作为指针参数? 
解: 
 算法如下
  void ClearStack (SeqStack *S)
   { // 删除栈中所有结点
    S->Top = -1; //其实只是将栈置空
   } 
  因为要置空的是栈S,如果不用指针来做参数传递,那么函数进行的操作不能对原来的栈产生影响,系统将会在内存中开辟另外的单元来对形参进行函数操作。结果等于什么也没有做。所以想要把函数操作的结果返回给实参的话,就只能用指针来做参数传递了。
关闭
 
3.8 利用栈的基本操作, 写一个返回S中结点个数的算法 int StackSize( SeqStack S),并说明S为何不作为指针参数? 
解:
 算法如下:
  int StackSize (SeqStack S)
   {//计算栈中结点个数
    int n=0;
    if(!EmptyStack(&S))
     {
      Pop(&S);
      n++;
     }
    return n;
   }
  上述算法的目的只要得到S栈的结点个数就可以了。并不能改变栈的结构。所以S不用指针做参数,以避免对原来的栈中元素进行任何改变。系统会把原来的栈按值传递给形参,函数只对形参进行操作,最后返回元素个数。
关闭
 
3.9 设计算法判断一个算术表达式的圆括号是否正确配对。 (提示: 对表达式进行扫描,凡遇到'('就进栈,遇')'就退掉栈顶的'(',表达式被扫描完毕,栈应为空。 
解:
  根据提示,可以设计算法如下:
 int PairBracket( char *SR)
  {//检查表达式ST中括号是否配对
   int i;
   SeqStack S; //定义一个栈
InitStack (&s);
   for (i=0; i<strlen(SR) ; i++)
    { 
     if ( S=='(' ) Push(&S, SR); //遇'('时进栈
     if ( S==')' ) //遇')'
      if (!StackEmpty(S))//栈不为空时,将栈顶元素出栈
       Pop(&s);
      else return 0;//不匹配,返回0
    }
   if EmptyStack(&s) return 1;// 匹配,返回1
   else return 0;//不匹配,返回0
  }
关闭
 
3.10 一个双向栈S是在同一向量空间内实现的两个栈,它们的栈底分别设在向量空间的两端。 试为此双向栈设计初始化InitStack ( S ) 、入栈Push( S , i , x) 和出栈Pop( S , i )等算法, 其中i为0 或1, 用以表示栈号。 
解:
  双向栈其实和单向栈原理相同,只是在一个向量空间内,好比是两个头对头的栈放在一起,中间的空间可以充分利用。双向栈的算法设计如下:
 //双向栈的栈结构类型与以前定义略有不同
 #define StackSize 100 // 假定分配了100个元素的向量空间
 #define char DataType
 typedef struct{
  DataType Data[StackSize]
int top0; //需设两个指针
  int top1;
 }DblStack
 void InitStack( DblStack *S )
  { //初始化双向栈
   S->top0 = -1;
   S->top1 = StackSize; //这里的top2也指出了向量空间,但由于是作为栈底,因此不会出错
  } 
 int EmptyStack( DblStack *S, int i )
  { //判栈空(栈号 i)
    return (i == 0 && S->top0 == -1|| i == 1 && S->top1== StackSize) ;
  }
 int FullStack( DblStack *S)
  { //判栈满,满时肯定两头相遇
   return (S->top0 == S-top1-1);
  }
 void Push(DblStack *S, int i, DataType x)
  { //进栈(栈号i)
   if (FullStack( S ))
    Error("Stack overflow");//上溢、退出运行
   if ( i == 0) S->Data[ ++ S->top0]= x; //栈0入栈
   if ( i == 1) S->Data[ -- S->top1]= x; // 栈1入栈
  }
 DataType Pop(DblStack *S, int i)
  { //出栈(栈号i)
   if (EmptyStack ( S,i) )
    Error("Stack underflow");//下溢退出
   if( i==0 ) 
    return ( S->Data[ S->top0--] );//返回栈顶元素,指针值减1
   if( i==1 )
    return ( S->Data[ S->top1++] ); //因为这个栈是以另一端为底的,所以指针值加1。
  }
关闭
 
3.11 Ackerman 函数定义如下:请写出递归算法。 
        ┌ n+1    当m=0时 
AKM ( m , n ) = │ AKM( m-1 ,1) 当m≠0 ,n=0时 
        └ AKM( m-1, AKM( m,n-1)) 当m≠0, n ≠ 0时 

解:
 算法如下
  int AKM( int m, int n)
   {
    if ( m== 0) return n+1;
    if ( m<>0 && n==0 ) return AKM( m-1, 1);
    if ( m<>0 && n<>0 ) return AKM( m-1, AKM( m, n-1));
   }
关闭
 
3.12 用第二种方法 ,即少用一个元素空间的方法来区别循环队列的队空和队满,试为其设计置空队,判队空,判队满、出队、入队及取队头元素等六个基本操作的算法。 
解:
 算法设计如下:
//循环队列的定义
#define QueueSize 100 
 typedef char Datatype ; //设元素的类型为char型
 typedef struct {
  int front;
  int rear;
  DataType Data[QueueSize];
}CirQueue;
(1)置空队
  void InitQueue ( CirQueue *Q)
   { // 置空队
    Q->front=Q->rear=0;
   }
 (2)判队空
  int EmptyQueue( CirQueue *Q)
   { //判队空
    return Q->front==Q->rear;
   }
 (3)判队满
  int FullQueue( CirQueue *Q)
{ // 判队满//如果尾指针加1后等于头指针,则认为满
    return (Q->rear+1)%QueueSize== Q->front;
   }
 (4)出队
  DataType DeQueue( CirQueue *Q)
   { //出队
    DataType temp;
    if(EmptyQueue(Q))
     Error("队已空,无元素可以出队");
    temp=Q->Data[Q->front] ;//保存元素值
    Q->front= ( Q->front+1 ) %QueueSize;//循环意义上的加1
    return temp; //返回元素值
   }
 (5)入队
  void EnQueue (CirQueue *Q, DataType x)
   { // 入队
    if( FullQueue( Q))
     Error ("队已满,不可以入队");
    Q->Data[Q->rear]=x; 
    Q->rear=(Q->rear+1)%QueueSize; //rear 指向下一个空元素位置
   }
 (6)取队头元素
  DataType FrontQueue( CirQueue *Q)
   { //取队头元素
    if (EmptyQueue( Q))
     Error( "队空,无元素可取");
    return Q->Data[Q->front];
   }
关闭
 
3.13 假设以带头结点的循环链表表示队列,并且只设一个指针指向队尾元素站点(注意不设头指针) ,试编写相应的置空队、判队空 、入队和出队等算法。 
解:
  算法如下:
 //先定义链队结构:
 typedef struct queuenode{
   Datatype data;
   struct queuenode *next;
  }QueueNode; //以上是结点类型的定义
 typedef struct{
   queuenode *rear;
  }LinkQueue; //只设一个指向队尾元素的指针
 (1)置空队
  void InitQueue( LinkQueue *Q)
   { //置空队:就是使头结点成为队尾元素
    QueueNode *s;
    Q->rear = Q->rear->next;//将队尾指针指向头结点
    while (Q->rear!=Q->rear->next)//当队列非空,将队中元素逐个出队
     {s=Q->rear->next;
      Q->rear->next=s->next;
      free(s);
     }//回收结点空间
   }
 (2)判队空 
  int EmptyQueue( LinkQueue *Q)
   { //判队空
    //当头结点的next指针指向自己时为空队
    return Q->rear->next->next==Q->rear->next;
   }
 (3)入队
  void EnQueue( LinkQueue *Q, Datatype x)
   { //入队
    //也就是在尾结点处插入元素
    QueueNode *p=(QueueNode *) malloc (sizeof(QueueNode));//申请新结点
    p->data=x; p->next=Q->rear->next;//初始化新结点并链入
    Q-rear->next=p; 
    Q->rear=p;//将尾指针移至新结点
   }
 (4)出队
  Datatype DeQueue( LinkQueue *Q)
   {//出队,把头结点之后的元素摘下
    Datatype t;
    QueueNode *p;
    if(EmptyQueue( Q ))
      Error("Queue underflow");
    p=Q->rear->next->next; //p指向将要摘下的结点
    x=p->data; //保存结点中数据
    if (p==Q->rear)
     {//当队列中只有一个结点时,p结点出队后,要将队尾指针指向头结点
      Q->rear = Q->rear->next; Q->rear->next=p->next;}
    else 
      Q->rear->next->next=p->next;//摘下结点p
    free(p);//释放被删结点
    return x;
   }
关闭
 
3.14 对于循环向量中的循环队列,写出求队列长度的公式。 
解:
  公式如下(设采用第二种方法,front指向真正的队首元素,rear指向真正队尾后一位置,向量空间大小:QueueSize
    Queuelen=(QueueSize+rear-front)%QueueSize
关闭
 
3.15 假设循环队列中只设rear和quelen 来分别指示队尾元素的位置和队中元素的个数,试给出判别此循环队列的队满条件,并写出相应的入队和出队算法,要求出队时需返回队头元素。 
解:
  根据题意,可定义该循环队列的存储结构:
 #define QueueSize 100 
 typedef char Datatype ; //设元素的类型为char型
 typedef struct {
   int quelen;
   int rear;
   Datatype Data[QueueSize];
  }CirQueue; 
 CirQueue *Q;
  循环队列的队满条件是:Q->quelen==QueueSize
  知道了尾指针和元素个数,当然就能计算出队头元素的位置。算法如下:
 (1)判断队满
   int FullQueue( CirQueue *Q)
    {//判队满,队中元素个数等于空间大小
      return Q->quelen==QueueSize;
    }
 (2)入队
   void EnQueue( CirQueue *Q, Datatype x)
    {// 入队
     if(FullQueue( Q))
      Error("队已满,无法入队");
     Q->Data[Q->rear]=x;
     Q->rear=(Q->rear+1)%QueueSize;//在循环意义上的加1
     Q->quelen++;
    }
 (3)出队
   Datatype DeQueue( CirQueue *Q)
    {//出队
     if(Q->quelen==0)
      Error("队已空,无元素可出队");
     int tmpfront; //设一个临时队头指针
     tmpfront=(QueueSize+Q->rear - Q->quelen+1)%QueueSize;//计算头指针位置
     Q->quelen--;
     return Q->Data[tmpfront];
    }
第四章 串
4.1 简述下列每对术语的区别:
  空串和空白串;串常量和串变量;主串和子串;静态分配的顺序串和动态分配的顺序串;目标串和模式串;有效位移和无效位移。
答:
 ●空串是指不包含任何字符的串,它的长度为零。
  空白串是指包含一个或多个空格的串,空格也是字符。
 ●串常量是指在程序中只可引用但不可改变其值的串。
  串变量是可以在运行中改变其值的。
 ●主串和子串是相对的,一个串中任意个连续字符组成的串就是这个串的子串,而包含子串的串就称为主串。
 ●静态分配的顺序串是指串的存储空间是确定的,即串值空间的大小是静态的,在编译时刻就被确定。
  动态分配的顺序串是在编译时不分配串值空间,在运行过程中用malloc和free等函数根据需要动态地分配和释放字符数组的空间(这个空间长度由分配时确定,也是顺序存储空间)。
 ●目标串和模式串:在串匹配运算过程中,将主串称为目标串,而将需要匹配的子串称为模式串,两者是相对的。
 ●有效位移和无效位移:在串定位运算中,模式串从目标的首位开始向右位移,每一次合法位移后如果模式串与目标中相应的字符相同,则这次位移就是有效位移(也就是从此位置开始的匹配成功),反之,若有不相同的字符存在,则此次位移就是无效位移(也就是从此位置开始的匹配失败)。

关闭
 
4.2 假设有如下的串说明:
  char s1[30]="Stocktom,CA", s2[30]="March 5 1999", s3[30], *p;
 (1)在执行如下的每个语句后p的值是什么?
  p=stchr(s1,'t'); p=strchr(s2,'9'); p=strchr(s2,'6');
 (2)在执行下列语句后,s3的值是什么?
  strcpy(s3,s1); strcat(s3,","); strcat(s3,s2);
 (3)调用函数strcmp(s1,s2)的返回值是什么?
 (4)调用函数strcmp(&s1[5],"ton")的返回值是什么?
 (5)调用函数stlen(strcat(s1,s2))的返回值是什么?
解:
 (1) stchr(*s,c)函数的功能是查找字符c在串s中的位置,若找到,则返回该位置,否则返回NULL。
 因此:
  执行p=stchr(s1,'t');后p的值是指向第一个字符t的位置, 也就是p==&s1[1]。
  执行p=strchr(s2,'9');后p的值是指向s2串中第一个9所在的位置,也就是p==&s2[9]。
`  执行p=strchr(s2,'6');之后,p的返回值是NULL。
 (2)strcpy函数功能是串拷贝,strcat函数的功能是串联接。所以:
  在执行strcpy(s3,s1); 后,s3的值是"Stocktom,CA"
  在执行strcat(s3,","); 后,s3的值变成"Stocktom,Ca,"
  在执行完strcat(s3,s2);后,s3的值就成了"Stocktom,Ca,March 5,1999"
 (3)函数strcmp(串1,串2)的功能是串比较,按串的大小进行比较,返回大于0,等于0或小于0的值以表示串1比串2 大,串1等于串2 ,串1小于串2。因此在调用函数strcmp(s1,s2)后,返回值是大于0的数(字符比较是以ascii码值相比的)
 (4)首先,我们要知道&s1[5]是一个地址,当放在函数strcmp中时,它就表示指向以它为首地址的一个字符串,所以在strcmp( &s1[5],"ton")中,前一个字符串值是"tom,CA",用它和"ton"比较,应该是后者更大,所以返回值是小于0的数。
 (5)strlen是求串长的函数,我们先将s1,s2联接起来,值是"Stocktom,CAMarch 5,1999",数一数有几个字符?是不是23个(空格也是一个)? 所以返回值是23。
关闭
 
4.3 设T[0..n-1]="adaabaabcaabaa",P[0..m-1]="aab".当用模式串匹配目标串T时,请给出所有的有效位移。算法NaiveStrMatch(T,P)返回的位移是哪一个位移。
解:
  所有的有效位移i的值为:2,5,9。
  算法NaveStrMatch(T,P)的返回值是第一个有效位移,因此是2。
关闭
 
4.4 利用C的库函数strlen,strcpy和strcat写一算法void StrInsert(char *S, char *T, int i),将串T插入到串S的第i个位置上。若i大于S的长度,则插入不执行。

 
解:
  算法如下:
 void StrInsert(char *S, char *T, int i)
  {//将串T插入到串S的第i个位置上
   char *Temp;
   if(i<=strlen(S))
    {
     Temp=(char *)malloc(sizeof(char[Maxsize]));// 设置一个临时串 
     strcpy(Temp,&S[i]);//将第i位起以后的字符拷贝到临时串中
     strcpy(&S[i], T);//将串T拷贝到串S的第i个位置处,覆盖后面的字符
     strcat(S,Temp);//把临时串中的字符联接到串S后面
     free( Temp );
    }
  }
关闭
 
4.5 利用C的库函数strlen 和strcpy(或strncpy)写一算法void StrDelete(char *S,int i, int m)删去串S中从位置i开始的连续m个字符。若i≥strlen(S),则没有字符被删除;若i+m≥strlen(S),则将S中从位置i开始直至末尾的字符均删去。
解:
  算法如下:
 void StrDelete(char *S, int i ,int m)
  { //串删除
   char Temp[Maxsize];//定义一个临时串
   if(i+m<strlen(S)) 
    {
     strcpy (Temp, &S[i+m]);//把删除的字符以后的字符保存到临时串中
     strcpy( &S[i],Temp);//用临时串中的字符覆盖位置i之后的字符
    }
   else if(i+m>=strlen(S)&& i<strlen(S))
    {
     strcpy(&S[i],"\0");//把位置i的元素置为'\0',表示串结束
    }
  }
关闭
 
4.6 以HString为存储表示,写一个求子串的算法。
解:
  HString 是指以动态分配顺序串为存储表示,其定义为:
   typedef struct {
    char *ch;
    int length;
   }HString;
  void *substr( HString *sub,HString *s,int pos,int len)
   {//用sub返回串s的第pos个字符起长度为len的子串。sub初始时为一空串
    //pos的合法位置为0<=pos<=s->length-1
    int i; 
    if (pos<0||pos>s->length-1||len<=0)
     Error("parameter error!");//参数不合法,子串为空串
    if (s->length<pos+len)//s串中没有足够的元素
     sub->len=s->length-pos;//设置子串的串长
    else sub->length=len; //设置子串的串长 
    sub->ch=(char *)malloc(len*sizeof(char));//为sub->ch申请结点空间
    for(i=0;i<sub->length;i++)//将s串中pos位置开始的共sub->length个字符复制到sub串中
     sub->ch[i]=s->ch[pos+i];
   }
关闭
 
4.7 一个文本串可用事先给定的字母映射表进行加密。例如,设字母映射表为:
  a b c d e f g h i j k l m n o p q r s t u v w x y z 
  n g z q t c o b m u h e l k p d a w x f y i v r s j 
  则字符串"encrypt"被加密为"tkzwsdf".试写一算法将输入的文本串进行加密后输出;另写一算法,将输入的已加密的文本串进行解密后输出。
解:
  加密算法可以用两个串中字符的一一对应关系来实现,当输入一个字符时,由算法在串Original中查找其位置,然后用串Cipher中相应位置的字符去替换原来的字符就可以了。解密算法则恰恰相返。
  设字母映射表的存储结构如下:
 #define MaxStrSize 26
 typedef struct{
   char Original[MaxStrSize]; //可容纳26个字符,并依次存储在Original[0..n]中
   char Cipher[MaxStrSize]; //可容纳26个字符,并依次对应Original表中的密码
   int length;
  }SeqString; 
 void Encrypt( SeqString codetable)
  {//加密算法。
   char ch;
   int i;
   while((ch=getchar())!='\0')
    { i=0;
     while (i<codetable.length&&ch!=codetable.Original[i])
       i++;
     if (i>=codetable.length)

 
        Error("data error!");
     else 
       printf("%c",codetable.Cipher[i]);
    }
   printf("\n");
  }
 void Decipher(SeqString Original , SeqString Cipher, char* T)
  {//解密算法。
   char ch;
   while((ch=getchar())!='\0')
    { i=0;
     while (i<codetable.length&&ch!=codetable.Cipher[i])
       i++;
     if (i>=codetable.length)
       Error("data error!");
     else 
       printf("%c",codetable.Original[i]);
    }
   printf("\n");
  }
关闭
 
4.8 写一算法void StrReplace(char *T, char *P, char *S),将T中首次出现的子串P替换为串S。注意:S和P的长度不一定相等。可以使用已有的串操作。
解:
  由于S和P的长度不一定相等,所以在替换时可能要移动字符元素。我们可以用到前面设计的一系列算法。算法如下:
 void StrReplace (char *T, char *P, char *S)
  {//串替换
   int i , m;
   m=strlen (P);//取得子串长度
   i=StrMatch(T,P);//取得串匹配位置
   StrDelete( T,i,m);//删除匹配处子串
   StrInsert( T, S,i);//将S串插入到匹配位置处
  }
关闭
 
4.9 将NaveStrMatch改写为输出目标串中所有与模式串匹配的有效位移。
解:
  把相应的返回语句改为打印输出就可找到所有匹配位置。改写后如下:
 void NaveStrMatch (SeqString T, SeqString P)
  {
   int i,j,k;
   int m=P.lenth;//模式串长度
   int n=T.length;//目标串长度
   for (i=0; i<n-m; i++) // 确定合法位移
    {
     j=0; k=i;
     while(j<m&&T.ch[k]==P.ch[j])
      {
       k++;j++;
      }
     if(j==m) printf("\n%d",i);
    }//endfor
   printf("Search End.");
  }
关闭
 
*4.10 利用4.9的结果写一算法void StrReplaceAll(char *T, char *P, char *S),将T中出现的所有与P相等的不重叠子串替换为S,这里S和P的长度不一定相等。
解:
  这个算法是具有实用性的,但是做起来比较难,简单的算法应是这样的,利用4.9的算法在串T中找到一个P的匹配成功,马上进行串替换,然后从替换后的下一个位置进行匹配,直到查找完所有的有效位移或者所有合法位移考查完毕也没有匹配成功。
  算法如下:
 void StrReplaceAll(char *T, char *P, char *S)
  {//全部替换
   int i, j, k ;
   i=0;
   while(i<strlen(T)-strlen(P)) //合法位移
    {
     j=0; k=i;
     while(T[k]==P[j]) //进行匹配
      {k++;j++;}
     if(j==m){ //匹配成功
       StrDelete( T,i,m);//删除匹配处子串
       StrInsert( T, S,i);//将S串插入到匹配位置处
       i+=strlen(S); //将查找位置移到替换后的下一个字符处,避免重复替换
      }//endif
     else i++;
    }//endwhile
  }//end
关闭
 
4.11 若S和T是用结点大小为1的单链表存储的两个串,试设计一个算法找出S中第一个不在T中出现的字符。
解:
  查找过程是这样的,取S中的一个字符(结点),然后和T中所有的字符一一比较,直到比完仍没有相同的字符时,查找过程结束,否则再取S中下一个字符,重新进行上述过程。算法如下:
  链串的结构类型定义:
 typedef struct node{
   char data;
   struct node *next;
  }LinkStrNode; //结点类型
 typedef LinkStrNode *LinkString; //LinkString为链串类型
 LinkString S; //S是链串的头指针
 char SearchNoin( LinkString S, LinkString T)
  {//查找不在T中出现的字符
   LinkStrNode *p,*q;
   p=S;
   q=T;
   while (p)
    { //取S中结点字符
      while(q&&p->data!=q->data)//进行字符比较
        q=q->next;
      if(q==NULL) return p->data;//找到并返回字符值
      q=T; //指针恢复串T的开始结点
      p=p->next;
    }
   printf("there's no such character.");
   return NULL;
  }
关闭
 

 第五章 数组与广义表
5.1请按行及按列优先顺序列出四维数组A2*3*2*3的所有元素在内存中的存储次序,开始结点为a0000 。 
解:
  按行优先的顺序排列时,先变化右边的下标,也就是右到左依次变化,这个四维数组的排列是这样的:(将这个排列分行写出以便与阅读,只要按从左到右的顺序存放就是在内存中的排列位置)
  a0000  a0001  a0002 
  a0010  a0011  a0012 
  a0100  
分享到:
评论

相关推荐

    数据结构答案_黄刘生

    数据结构答案,针对黄刘生编写的2331,

    数据结构课后答案(黄刘生版).doc

    自考数据结构课后习题答案 数 据 结 构 第一章 1.1 简述下列概念:数据、数据元素、数据类型、数据结构、逻辑结构、存储结构、线性结构、非线性结构。 ◆ 数据:指能够被计算机识别、存储和加工处理的信息载体。 ◆...

    数据结构-用c语言描述答案 唐善策 李龙澎 黄刘生主编

    《数据结构-用C语言描述答案》由唐善策、李龙澎和黄刘生主编,是一本针对这一主题的优秀教材,提供了完整的答案,对于学习者来说是非常有价值的资源。 本书分为九个章节,涵盖了数据结构的基础和关键概念: 1. **...

    自考数据结构课后习题答案

    在《2011年自考数据结构课后习题答案_黄刘生.doc》文档中,可能涵盖了上述所有知识点的习题解答,包括基础概念的辨析、算法的设计与分析、复杂度计算等。通过解题,学生能够加深对数据结构的理解,掌握如何利用数据...

    《数据结构》课后习题答案

    自考黄刘生版《数据结构》课后习题答案 第一章数据结构 1.1 简述下列概念:数据、数据元素、数据类型、数据结构、逻辑结构、存储结构、线性结构、非线性结构。

    中国科技大学黄刘生老师课件

    黄刘生老师的算法设计课程ppt,适合本科研究生使用

    数据结构PPT(2).ppt

    课程选用的教材是唐策善、李龙澎、黄刘生合著的《数据结构--用C语言描述》,这是一本详细介绍了数据结构与C语言结合的著作。此外,还推荐了严蔚敏、吴伟民的《数据结构(C语言版)》以及Ellis Horowitz、Sartaj ...

    数据结构与数据库PPT学习教案.pptx

    还推荐了《数据结构(第二版)》、"Data Structures with C++"和"Data Structures & Program Design in C"等书籍,分别由唐策善、黄刘生以及William Ford等人撰写,这些书提供了不同的编程语言视角来看待数据结构。...

    数据结构 用C语言描述

    数据结构是计算机科学中至关重要的基础课程,它探讨如何有效地组织和管理数据,以便于高效地进行各种操作。本主题以C语言为编程工具,详细介绍了数据结构的概念和实现方法。这里我们将深入讨论《数据结构》一书中的...

    《数据结构——用c语言描述》的习题答案

    高等教育出版社出版的 ,有用,答案也比较详细

    中科大黄刘生教授分布式算法、概率算法讲义

    2. 拉斯维加斯算法:这类算法会返回正确答案,但运行时间具有随机性,成功概率可调。 3. 厄米特算法:结合确定性和随机性,可以保证在有限步内得到近似解或精确解。 4. 概率分析:评估算法性能时,通过概率论方法...

    中科大 算法导论 课件(全套2)函数的增长

    根据提供的信息,我们可以总结出以下知识点: ### 中科大 算法导论 课件(全套2)函数的增长 #### 课程概述 这门课程是中国科学技术大学算法导论的一部分,由庄连生教授主讲,针对计算机相关专业的学生开设。...

    中科大 算法导论 课件(全套7)顺序统计学

    根据给定文件的信息,我们可以提炼出以下相关的IT知识点: ### 中科大算法导论课程——顺序统计学 #### 一、课程概述 本课程为中国科学技术大学计算机相关专业的必修课,由庄连生教授主讲,针对算法的基础理论...

    中科大_研究生_算法设计与分析_2020课后作业答案.rar

    中国科学技术大学计算机专业研究生必修课程《算法设计与分析》(教师:黄刘生, 汪炀)2020年课后作业个人解答(其中代码题目用python实现),以及助教给出的部分题目答案和习题课课件,还包含了往年作业的参考答案。

    中科大算法导论课程实验 常见排序算法的实现与性能比较 代码

    在本实验中,我们主要探讨的是排序算法,这是计算机科学中的一个重要领域,特别是在数据处理和数据分析方面。排序算法是用于将一组数据按照特定顺序排列的算法。以下将详细阐述涉及的几种排序算法及其原理。 1. **...

    概率算法课件

    概率算法 课件 --中国科学技术大学研究生算法课件 黄刘生教授讲授

    中科大 算法导论 课件(全套5)概率分析与随机算法

    而在概率分析与随机算法中,我们考虑的是算法运行的时间不仅与输入规模有关,还与输入数据的具体分布有关。这种分析方法更加符合实际情况,因为实际应用中的数据往往具有不确定性。 #### 雇用问题 雇用问题是概率...

Global site tag (gtag.js) - Google Analytics