數據結構實踐(有的數據結構課后習題答案),紅色是答案


1章  緒論

習題

1.簡述下列概念:數據、數據元素、數據項、數據對象、數據結構、邏輯結構、存儲結構、抽象數據類型。

2.試舉一個數據結構的例子,敘述其邏輯結構和存儲結構兩方面的含義和相互關系。

3.簡述邏輯結構的四種基本關系並畫出它們的關系圖。

4.存儲結構由哪兩種基本的存儲方法實現?

5選擇題

1)在數據結構中,從邏輯上可以把數據結構分成(   )。

A.動態結構和靜態結構     B.緊湊結構和非緊湊結構

C線性結構和非線性結構   D.內部結構和外部結構

2)與數據元素本身的形式、內容、相對位置、個數無關的是數據的(   )。

A.存儲結構               B.存儲實現

C.邏輯結構               D.運算實現

3)通常要求同一邏輯結構中的所有數據元素具有相同的特性,這意味着(   )。

   A.數據具有同一特點

B不僅數據元素所包含的數據項的個數要相同,而且對應數據項的類型要一致

C.每個數據元素都一樣

D.數據元素所包含的數據項的個數要相等

4)以下說法正確的是(   )。

A.數據元素是數據的最小單位

B.數據項是數據的基本單位

C.數據結構是帶有結構的各數據項的集合

D一些表面上很不相同的數據可以有相同的邏輯結構

5以下與數據的存儲結構無關的術語是(   )。

A.順序隊列       B. 鏈表        C. 有序表          D.  鏈棧

6以下數據結構中,(  )是非線性數據結構

A.樹        B.字符串       C.隊           D.棧

6.試分析下面各程序段的時間復雜度。

1x=90; y=100; 

while(y>0)

if(x>100)

 {x=x-10;y--;}

else x++;

2for (i=0;  i<n; i++)

for (j=0; j<m; j++)

a[i][j]=0;

3s=0;

     for i=0; i<n; i++)

for(j=0; j<n; j++)

         s+=B[i][j];

sum=s;

4i=1;

     while(i<=n)

        i=i*3;

5x=0;

for(i=1; i<n; i++)

   for (j=1; j<=n-i; j++)

x++;

6x=n; //n>1

y=0;

while(x≥(y+1)* (y+1))

    y++;

 

1O1

2Om*n

3On2

4Olog3n

5)因為x++共執行了n-1+n-2+……+1= n(n-1)/2,所以執行時間為On2

6O()


2章  線性表

1選擇題

1一個向量第一個元素的存儲地址是100,每個元素的長度為2,則第5個元素的地址是(   )。

A.110            B108         C.100          D.120

2n個結點的順序表中,算法的時間復雜度是O(1)的操作是(   )。

A訪問第i個結點(1in)和求第i個結點的直接前驅(2in) 

B.在第i個結點后插入一個新結點(1in

C.刪除第i個結點(1in

Dn個結點從小到大排序

3 向一個有127個元素的順序表中插入一個新元素並保持原來順序不變,平均要移動  的元素個數為(   )。

A.8      B63.5        C.63      D.7

4鏈接存儲的存儲結構所占存儲空間(   )。

A分兩部分,一部分存放結點值,另一部分存放表示結點間關系的指針

B.只有一部分,存放結點值

C.只有一部分,存儲表示結點間關系的指針

D.分兩部分,一部分存放結點值,另一部分存放結點所占單元數

5線性表若采用鏈式存儲結構時,要求內存中可用存儲單元的地址(   )。

A必須是連續的        B.部分地址必須是連續的

C.一定是不連續的      D連續或不連續都可以

6線性表L在(   )情況下適用於使用鏈式結構實現。

A需經常修改L中的結點值      .需不斷對L進行刪除插入 

C.L中含有大量的結點          D.L中結點結構復雜

7單鏈表的存儲密度(   )。

A大於1        B等於1      C小於1    D不能確定

8)將兩個各有n個元素的有序表歸並成一個有序表,其最少的比較次數是(   )。

An            B.2n-1        C.2n        D.n-1

9在一個長度為n的順序表中,在第i個元素(1≤i≤n+1)之前插入一個新元素時須向后移動(   )個元素。

A.n-i           B.n-i+1       C.n-i-1      D.i

(10) 線性表L=(a1,a2,……an),下列說法正確的是(   )。

A.每個元素都有一個直接前驅和一個直接后繼

B.線性表中至少有一個元素

C.表中諸元素的排列必須是由小到大或由大到小

D.除第一個和最后一個元素外,其余每個元素都有一個且僅有一個直接前驅和直接后繼。

(11) 若指定有n個元素的向量,則建立一個有序單鏈表的時間復雜性的量級是(   )。

A.O(1)          B.O(n)            CO(n2)          D.O(nlog2n)

(12) 以下說法錯誤的是(   )。

A.求表長、定位這兩種運算在采用順序存儲結構時實現的效率不比采用鏈式存儲結構時實現的效率低

B.順序存儲的線性表可以隨機存取

C.由於順序存儲要求連續的存儲區域,所以在存儲管理上不夠靈活

D.線性表的鏈式存儲結構優於順序存儲結構

(13) 在單鏈表中,要將s所指結點插入到p所指結點之后,其語句應為(   )。

A.s->next=p+1; p->next=s;

B.(*p).next=s; (*s).next=(*p).next;

C.s->next=p->next; p->next=s->next;

D.s->next=p->next; p->next=s;        

 (14) 在雙向鏈表存儲結構中,刪除p所指的結點時須修改指針(   )。

A.p->next->prior=p->prior; p->prior->next=p->next;

B.p->next=p->next->next; p->next->prior=p;

C.p->prior->next=p; p->prior=p->prior->prior;

D.p->prior=p->next->next; p->next=p->prior->prior;

(15) 在雙向循環鏈表中,在p指針所指的結點后插入q所指向的新結點,其修改指針的操作是(   )。

A.p->next=q; q->prior=p; p->next->prior=q; q->next=q;

B.p->next=q; p->next->prior=q; q->prior=p; q->next=p->next;

C.q->prior=p; q->next=p->next; p->next->prior=q; p->next=q;

D.q->prior=p; q->next=p->next; p->next=q; p->next->prior=q;

 

2.算法設計題

1將兩個遞增的有序鏈表合並為一個遞增的有序鏈表。要求結果鏈表仍使用原來兩個鏈表的存儲空間, 不另外占用其它的存儲空間。表中不允許有重復的數據。

void MergeList_L(LinkList &La,LinkList &Lb,LinkList &Lc){

   pa=La->next;  pb=Lb->next;

   Lc=pc=La;             //用La的頭結點作為Lc的頭結點 

   while(pa && pb){

      if(pa->data<pb->data){ pc->next=pa;pc=pa;pa=pa->next;}

      else if(pa->data>pb->data) {pc->next=pb; pc=pb; pb=pb->next;}

      else {// 相等時取La的元素,刪除Lb的元素

               pc->next=pa;pc=pa;pa=pa->next;

               q=pb->next;delete pb ;pb =q;}

   }

   pc->next=pa?pa:pb;    //插入剩余段  

   delete Lb;             //釋放Lb的頭結點}  

2將兩個非遞減的有序鏈表合並為一個非遞增的有序鏈表。要求結果鏈表仍使用原來兩個鏈表的存儲空間, 不另外占用其它的存儲空間。表中允許有重復的數據。

void union(LinkList& La, LinkList& Lb, LinkList& Lc, ) {

  pa = La->next;  pb = Lb->next;              // 初始化

  Lc=pc=La; //用La的頭結點作為Lc的頭結點 

  Lc->next = NULL;

  while ( pa || pb ) {

     if  ( !pa )  { q = pb;  pb = pb->next; }

     else if  ( !pb )  { q = pa;  pa = pa->next; } 

     else if (pa->data <= pb->data )     { q = pa;  pa = pa->next; }

     else  { q = pb;  pb = pb->next; }

     q->next = Lc->next;  Lc->next = q;    // 插入

  }

  delete Lb;             //釋放Lb的頭結點}   

3已知兩個鏈表A和B分別表示兩個集合,其元素遞增排列。請設計算法求出A與B的交集,並存放於A鏈表中。

void Mix(LinkList& La, LinkList& Lb, LinkList& Lc, ) {

pa=la->next;pb=lb->next;∥設工作指針papb

Lc=pc=La; //用La的頭結點作為Lc的頭結點

while(pa&&pb) 

  if(pa->data==pb->data)∥交集並入結果表中。

   { pc->next=pa;pc=pa;pa=pa->next;

 u=pb;pb=pb->next; delete u;}

 else if(pa->data<pb->data) {u=pa;pa=pa->next; delete u;}

else {u=pb; pb=pb->next; delete u;}

while(pa){ u=pa; pa=pa->next; delete u;}∥ 釋放結點空間

while(pb) {u=pb; pb=pb->next; delete u;}∥釋放結點空間

pc->next=null;∥置鏈表尾標記。

delete Lb;   ∥注: 本算法中也可對B表不作釋放空間的處理

 

4已知兩個鏈表A和B分別表示兩個集合,其元素遞增排列。請設計算法求出兩個集合A和B 的差集(即僅由在A中出現而不在B中出現的元素所構成的集合),並以同樣的形式存儲,同時返回該集合的元素個數。

void  DifferenceLinkedList  AB*n

AB均是帶頭結點的遞增有序的單鏈表,分別存儲了一個集合,本算法求兩集合的差集,存儲於單鏈表A中,*n是結果集合中元素個數,調用時為0

{p=A->next                  pq分別是鏈表AB的工作指針。

 q=B->next pre=A          preAp所指結點的前驅結點的指針。

 whilep!=null && q!=null

ifp->data<q->data{pre=pp=p->next*n++ A鏈表中當前結點指針后移。

else ifp->data>q->dataq=q->next     B鏈表中當前結點指針后移。

else {pre->next=p->next         ∥處理AB中元素值相同的結點,應刪除。

      u=p p=p->next delete u}   ∥刪除結點

 

5設計算法將一個帶頭結點的單鏈表A分解為兩個具有相同結構的鏈表B、C,其中B表的結點為A表中值小於零的結點,而C表的結點為A表中值大於零的結點(鏈表A的元素類型為整型,要求B、C表利用A表的結點)。

6設計一個算法,通過一趟遍歷在單鏈表中確定值最大的結點。

ElemType Max (LinkList L ){

if(L->next==NULL) return NULL;

pmax=L->next; //假定第一個結點中數據具有最大值

p=L->next->next;

while(p != NULL ){//如果下一個結點存在

if(p->data > pmax->data) pmax=p;

p=p->next;

}

return pmax->data;

 

7設計一個算法,通過遍歷一趟,將鏈表中所有結點的鏈接方向逆轉,仍利用原表的存儲空間。

void  inverse(LinkList &L) {

    // 逆置帶頭結點的單鏈表 L

    p=L->next;  L->next=NULL;

    while ( p) {

        q=p->next;    // q指向*p的后繼

        p->next=L->next;

        L->next=p;       // *p插入在頭結點之后

        p = q;

    }

}

8設計一個算法,刪除遞增有序鏈表中值大於mink且小於maxk的所有元素(mink和maxk是給定的兩個參數,其值可以和表中的元素相同,也可以不同 )。

void delete(LinkList &L, int mink, int maxk) {

   p=L->next; //首元結點

   while (p && p->data<=mink)

      { pre=p;  p=p->next; } //查找第一個值>mink的結點

   if (p) {

       while (p && p->data<maxk)  p=p->next;

                      // 查找第一個值 ≥maxk 的結點

      q=pre->next;   pre->next=p;  // 修改指針

      while (q!=p) 

         { s=q->next;  delete q;  q=s; } // 釋放結點空間

   }//if

}

9已知p指向雙向循環鏈表中的一個結點,其結點結構為data、prior、next三個域,寫出算法change(p),交換p所指向的結點和它的前綴結點的順序。

知道雙向循環鏈表中的一個結點,與前驅交換涉及到四個結點(p結點,前驅結點,前驅的前驅結點,后繼結點)六條鏈。

void  ExchangeLinkedList p

p是雙向循環鏈表中的一個結點,本算法將p所指結點與其前驅結點交換。

{q=p->llink

 q->llink->rlink=p   p的前驅的前驅之后繼為p

 p->llink=q->llink   p的前驅指向其前驅的前驅。

 q->rlink=p->rlink   p的前驅的后繼為p的后繼。

 q->llink=p          p與其前驅交換

 p->rlink->llink=q   p的后繼的前驅指向原p的前驅

 p->rlink=q          p的后繼指向其原來的前驅

}∥算法exchange結束。

 

10已知長度為n的線性表A采用順序存儲結構,請寫一時間復雜度為O(n)、空間復雜度為O(1)的算法,該算法刪除線性表中所有值為item的數據元素。

[題目分析在順序存儲的線性表上刪除元素,通常要涉及到一系列元素的移動(刪第i個元素,第i+1至第n個元素要依次前移)。本題要求刪除線性表中所有值為item的數據元素,並未要求元素間的相對位置不變。因此可以考慮設頭尾兩個指針(i=1j=n),從兩端向中間移動,凡遇到值item的數據元素時,直接將右端元素左移至值為item的數據元素位置。

void  DeleteElemType A[ ]int  n

A是有n個元素的一維數組,本算法刪除A中所有值為item的元素。

{i=1j=n;∥設置數組低、高端指針(下標)。

 whilei<j

   {whilei<j && A[i]!=itemi++         ∥若值不為item,左移指針。

    ifi<jwhilei<j && A[j]==itemj--;∥若右端元素值為item,指針左移

    ifi<jA[i++]=A[j--]

   }

[算法討論] 因元素只掃描一趟,算法時間復雜度為O(n)。刪除元素未使用其它輔助空間,最后線性表中的元素個數是j。


3章  棧和隊列

習題

1.選擇題

1若讓元素12345依次進棧,則出棧次序不可能出現在(  )種情況。

A.54321   B.21543    C.4312   D.23541

2)若已知一個棧的入棧序列是123,…,n,其輸出序列為p1,p2,p3,…,pn,若p1=n,則pi(  )。

    A.i               B.n-i               C.n-i+1            D.不確定

3)數組Q[n]用來表示一個循環隊列,f為當前隊列頭元素的前一位置,r為隊尾元素的位置,假定隊列中元素的個數小於n,計算隊列中元素個數的公式為(  )。

A.r-f             B.(n+f-r)%n       C.n+r-f           D.n+r-f)%n

4)鏈式棧結點為:(data,link),top指向棧頂.若想摘除棧頂結點,並將刪除結點的值保存到x,則應執行操作(  )。

A.x=top->data;top=top->link;   B.top=top->link;x=top->link;    

C.x=top;top=top->link       D.x=top->link

5設有一個遞歸算法如下

        int fact(int n) {  //n大於等於0

             if(n<=0) return 1;

             else return n*fact(n-1);        }

則計算fact(n)需要調用該函數的次數為(  )。 

A. n+1              B. n-1              C. n                  D. n+2

6棧在 (  )中有所應用。

A.遞歸調用       B.函數調用      C.表達式求值        D.前三個選項都有

7為解決計算機主機與打印機間速度不匹配問題,通常設一個打印數據緩沖區。主機將要輸出的數據依次寫入該緩沖區,而打印機則依次從該緩沖區中取出數據。該緩沖區的邏輯結構應該是(  )。

A.隊列           B.棧            C. 線性表           D.有序表

8設棧S和隊列Q的初始狀態為空,元素e1e2e3e4e5e6依次進入棧S,一個元素出棧后即進入Q,若6個元素出隊的序列是e2e4e3e6e5e1,則棧S的容量至少應該是( )。

A.2              B.3              C.4                D. 6

9在一個具有n個單元的順序棧中,假設以地址高端作為棧底,以top作為棧頂指針,則當作進棧處理時,top的變化為( )。 

A.top不變        B.top=0           C.top--              D.top++

10設計一個判別表達式中左,右括號是否配對出現的算法,采用( )數據結構最佳。

A.線性表的順序存儲結構              B.隊列     

C. 線性表的鏈式存儲結構              D. 

11用鏈接方式存儲的隊列,在進行刪除運算時( )。

A. 僅修改頭指針                      B. 僅修改尾指針

C. 頭、尾指針都要修改                D. 頭、尾指針可能都要修改

12循環隊列存儲在數組A[0..m]中,則入隊時的操作為( )。

A. rear=rear+1                       B. rear=(rear+1)%(m-1)

  C. rear=(rear+1)%m                   D. rear=(rear+1)%(m+1) 

13最大容量為n的循環隊列,隊尾指針是rear,隊頭是front,則隊空的條件是( )。

  A. (rear+1)%n==front                  B. rear==front                                                          

C.rear+1==front                      D. (rear-l)%n==front

14棧和隊列的共同點是( )。

A. 都是先進先出                       B. 都是先進后出   

C. 只允許在端點處插入和刪除元素       D. 沒有共同點

15一個遞歸算法必須包括( )。

A. 遞歸部分                           B. 終止條件和遞歸部分

C. 迭代部分                           D. 終止條件和迭代部分

 

2回文是指正讀反讀均相同的字符序列,如“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[i]);
   while( !EmptyStack( &s))
    {// 每彈出一個字符與相應字符比較
     temp=Pop (&s);
     if( temp!=S[i])  return 0 ;// 不等則返回0
     else i++;
    } 
   return 1 ; // 比較完畢均相等則返回 1
  }

3設從鍵盤輸入一整數的序列:a1, a2, a3,…,an,試編寫算法實現:用棧結構存儲輸入的整數,當ai≠-1時,將ai進棧;當ai=-1時,輸出棧頂整數並出棧。算法應對異常情況(入棧滿等)給出相應的信息。

#define maxsize 棧空間容量

   void InOutS(int s[maxsize])

      //s是元素為整數的棧,本算法進行入棧和退棧操作。

   {int top=0;             //top為棧頂指針,定義top=0時為棧空。

    for(i=1; i<=n; i++)    //n個整數序列作處理。

     {scanf(“%d”,&x);    //從鍵盤讀入整數序列。

      if(x!=-1)           // 讀入的整數不等於-1時入棧。

       if(top==maxsize-1){printf(“棧滿\n”);exit(0);}else s[++top]=x; //x入棧。

       else   //讀入的整數等於-1時退棧。

       {if(top==0){printf(“棧空\n”);exit(0);} else printf(“出棧元素是%d\n”,s[top--])}}

     }//算法結束。

 

4從鍵盤上輸入一個后綴表達式,試編寫算法計算表達式的值。規定:逆波蘭表達式的長度不超過一行,以$符作為輸入結束,操作數之間用空格分隔,操作符只可能有+、-、*、/四種運算。例如:234 34+2*$。

 [題目分析]逆波蘭表達式(即后綴表達式)求值規則如下:設立運算數棧OPND,對表達式從左到右掃描(讀入),當表達式中掃描到數時,壓入OPND棧。當掃描到運算符時,從OPND退出兩個數,進行相應運算,結果再壓入OPND棧。這個過程一直進行到讀出表達式結束符$,這時OPND棧中只有一個數,就是結果。

   float expr( )

//從鍵盤輸入逆波蘭表達式,以‘$’表示輸入結束,本算法求逆波蘭式表達式的值。

float OPND[30];   // OPND是操作數棧。

init(OPND);       //兩棧初始化。

  float num=0.0;    //數字初始化。

  scanf (“%c”,&x);//x是字符型變量。

  while(x!=’$’)

   {switch

      {case0’<=x<=’9’:while((x>=’0’&&x<=’9’)||x==’.’)  //拼數

                           if(x!=’.’)   //處理整數

{num=num*10+ord(x)-ord(‘0’); scanf(“%c”,&x);}

                           else           //處理小數部分。

                            {scale=10.0; scanf(“%c”,&x);

                             while(x>=’0’&&x<=’9’)

                               {num=num+(ord(x)-ord(‘0’)/scale;

                                scale=scale*10;  scanf(“%c”,&x); }

                             }//else

                             push(OPND,num); num=0.0;//數壓入棧,下個數初始化

       case x= ’:break;  //遇空格,繼續讀下一個字符。

       case x=+’:push(OPND,pop(OPND)+pop(OPND));break;

       case x=-:x1=pop(OPND);x2=pop(OPND);push(OPND,x2-x1);break;

       case x=*’:push(OPND,pop(OPND)*pop(OPND));break;

       case x=/:x1=pop(OPND);x2=pop(OPND);push(OPND,x2/x1);break;

       default:       //其它符號不作處理。

     }//結束switch

     scanf(“%c”,&x);//讀入表達式中下一個字符。

   }//結束whilex=$’

  printf(“后綴表達式的值為%f”,pop(OPND));

}//算法結束。

[算法討論]假設輸入的后綴表達式是正確的,未作錯誤檢查。算法中拼數部分是核心。若遇到大於等於‘0’且小於等於‘9’的字符,認為是數。這種字符的序號減去字符‘0’的序號得出數。對於整數,每讀入一個數字字符,前面得到的部分數要乘上10再加新讀入的數得到新的部分數。當讀到小數點,認為數的整數部分已完,要接着處理小數部分。小數部分的數要除以10(或10的冪數)變成十分位,百分位,千分位數等等,與前面部分數相加。在拼數過程中,若遇非數字字符,表示數已拼完,將數壓入棧中,並且將變量num恢復為0,准備下一個數。這時對新讀入的字符進入‘+’、‘-’、‘*’、‘/’及空格的判斷,因此在結束處理數字字符的case后,不能加入break語句。

 

5假設以I和O分別表示入棧和出棧操作。棧的初態和終態均為空,入棧和出棧的操作序列可表示為僅由I和O組成的序列,稱可以操作的序列為合法序列,否則稱為非法序列。

①下面所示的序列中哪些是合法的?

   A. IOIIOIOO     B. IOOIOIIO      C. IIIOIOIO     D. IIIOOIOO

②通過對①的分析,寫出一個算法,判定所給的操作序列是否合法。若合法,返回true,否則返回false(假定被判定的操作序列已存入一維數組中)。

 

AD是合法序列,B是非法序列。

設被判定的操作序列已存入一維數組A中。

       int Judge(char A[])

        //判斷字符數組A中的輸入輸出序列是否是合法序列。如是,返回true,否則返回false

        {i=0;                //i為下標。

         j=k=0;              //jk分別為I和字母O的的個數。

         while(A[i]!=\0’) //當未到字符數組尾就作。

           {switch(A[i])

             {caseI’: j++; break; //入棧次數增1

              caseO’: k++; if(k>j){printf(“序列非法\n)exit(0);}

              }

i++; //不論A[i]是‘I’或‘O’,指針i均后移。}

         if(j!=k) {printf(“序列非法\n)return(false);}

         else {printf(“序列合法\n)return(true);}

        }//算法結束。

     [算法討論]在入棧出棧序列(即由‘I’和‘O’組成的字符串)的任一位置,入棧次數(‘I’的個數)都必須大於等於出棧次數(即‘O’的個數),否則視作非法序列,立即給出信息,退出算法。整個序列(即讀到字符數組中字符串的結束標記‘\0’),入棧次數必須等於出棧次數(題目中要求棧的初態和終態都為空),否則視為非法序列。

(6)假設以帶頭結點的循環鏈表表示隊列,並且只設一個指針指向隊尾元素站點(注意不設頭指針) ,試編寫相應的置空隊、判隊空 、入隊和出隊等算法。 

算法如下:
 //先定義鏈隊結構:
 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;
   }

7假設以數組Q[m]存放循環隊列中的元素, 同時設置一個標志tag,以tag == 0和tag == 1來區別在隊頭指針(front)和隊尾指針(rear)相等時,隊列狀態為“空”還是“滿”。試編寫與此結構相應的插入(enqueue)和刪除(dlqueue)算法。

【解答】

循環隊列類定義

#include <assert.h>

template <class Type> class Queue {//循環隊列的類定義

public: 

   Queue ( int=10 );

   ~Queue ( ) { delete [ ] Q; }

   void EnQueue ( Type & item );

   Type DeQueue ( );

   Type GetFront ( );

   void MakeEmpty ( ) front = rear = tag = 0; }//置空隊列

   int IsEmpty ( ) const { return front == rear && tag == 0; }//判隊列空否

   int IsFull ( ) const { return front == rear && tag == 1; }//判隊列滿否

private:

   int rear, fronttag;//隊尾指針、隊頭指針和隊滿標志

   Type *Q;//存放隊列元素的數組

   int m;//隊列最大可容納元素個數

}

構造函數

template <class Type>

Queue<Type>:: Queue ( int sz ) : rear (0), front (0), tag(0), m (sz{

//建立一個最大具有m個元素的空隊列。

   Q = new Type[m];//創建隊列空間

   assert ( Q != 0 );//斷言: 動態存儲分配成功與否

}

插入函數

template<class Type> 

void Queue<Type:: EnQueue Type &item {

assert ( ! IsFull ( ) );//判隊列是否不滿,滿則出錯處理

rear = ( rear + 1 ) % m;//隊尾位置進1, 隊尾指針指示實際隊尾位置

Q[rear] = item;//進隊列

tag = 1;//標志改1,表示隊列不空

}

刪除函數

template<class Type> 

Type Queue<Type:: DeQueue ( ) {

assert ( ! IsEmpty ( ) );//判斷隊列是否不空,空則出錯處理

front = ( front + 1 ) % m;//隊頭位置進1, 隊頭指針指示實際隊頭的前一位置

tag = 0;//標志改0, 表示棧不滿

return Q[front];//返回原隊頭元素的值

}

讀取隊頭元素函數

template<class Type> 

Type Queue<Type:: GetFront ( ) {

assert ( ! IsEmpty ( ) );//判斷隊列是否不空,空則出錯處理

return Q[(front + 1) % m];//返回隊頭元素的值

}

 

(8)如果允許在循環隊列的兩端都可以進行插入和刪除操作。要求:

① 寫出循環隊列的類型定義;

② 寫出“從隊尾刪除”和“從隊頭插入”的算法。

[題目分析用一維數組 v[0..M-1]實現循環隊列,其中M是隊列長度。設隊頭指針 front和隊尾指針rear,約定front指向隊頭元素的前一位置,rear指向隊尾元素。定義front=rear時為隊空,(rear+1)%m=front 為隊滿。約定隊頭端入隊向下標小的方向發展,隊尾端入隊向下標大的方向發展。

1#define M  隊列可能達到的最大長度

typedef struct

  { elemtp data[M];

    int front,rear;

  } cycqueue;

2elemtp delqueue ( cycqueue Q)

     //Q是如上定義的循環隊列,本算法實現從隊尾刪除,若刪除成功,返回被刪除元素,否則給出出錯信息。

if (Q.front==Q.rear)  {printf(“隊列空”); exit(0);}

  Q.rear=(Q.rear-1+M)%M;          //修改隊尾指針。

        return(Q.data[(Q.rear+1+M)%M]); //返回出隊元素。

}//從隊尾刪除算法結束

void enqueue (cycqueue Q, elemtp x)

// Q是順序存儲的循環隊列,本算法實現“從隊頭插入”元素x

{if (Q.rear==(Q.front-1+M)%M)  {printf(“隊滿”; exit(0);)

 Q.data[Q.front]=x;        //x 入隊列

Q.front=(Q.front-1+M)%M;    //修改隊頭指針。

}// 結束從隊頭插入算法。

 

(9)已知Ackermann函數定義如下:

   

① 寫出計算Ack(m,n)的遞歸算法,並根據此算法給出出Ack(2,1)的計算過程。

② 寫出計算Ack(m,n)的非遞歸算法。

int Ack(int m,n)

    {if (m==0) return(n+1);

     else if(m!=0&&n==0) return(Ack(m-1,1));

         else return(Ack(m-1,Ack(m,m-1));

     }//算法結束

1Ack(2,1)的計算過程

     Ack(2,1)=Ack(1,Ack(2,0))           //m<>0,n<>0而得

            =Ack(1,Ack(1,1))            //m<>0,n=0而得

            =Ack(1,Ack(0,Ack(1,0)))     // m<>0,n<>0而得

            = Ack(1,Ack(0,Ack(0,1)))    // m<>0,n=0而得

            =Ack(1,Ack(0,2))            // m=0而得

            =Ack(1,3)                   // m=0而得

            =Ack(0,Ack(1,2))            //m<>0,n<>0而得

            = Ack(0,Ack(0,Ack(1,1)))    //m<>0,n<>0而得

            = Ack(0,Ack(0,Ack(0,Ack(1,0)))) //m<>0,n<>0而得

            = Ack(0,Ack(0,Ack(0,Ack(0,1)))) //m<>0,n=0而得

            = Ack(0,Ack(0,Ack(0,2)))    //m=0而得

            = Ack(0,Ack(0,3))           //m=0而得

            = Ack(0,4)                  //n=0而得

            =5                          //n=0而得

2int Ackerman( int m, int n)

       {int akm[M][N];int i,j;

        for(j=0;j<N;j++) akm[0][j];=j+1;

        for(i=1;i<m;i++)

          {akm[i][0]=akm[i-1][1];

           for(j=1;j<N;j++)

             akm[i][j]=akm[i-1][akm[i][j-1]];

           }

          return(akm[m][n]);

        }//算法結束

 

(10)已知f為單鏈表的表頭指針, 鏈表中存儲的都是整型數據,試寫出實現下列運算的遞歸算法:

① 求鏈表中的最大整數;

② 求鏈表的結點個數;

③ 求所有整數的平均值。

#include <iostream.h>//定義在頭文件"RecurveList.h"

class List;

class ListNode {//鏈表結點類

friend class List;

private:

int data;//結點數據

ListNode *link;//結點指針

ListNode ( const int item ) : data(item), link(NULL{ }//構造函數

};

class List {//鏈表類

private:

ListNode *firstcurrent;

int Max ( ListNode *f );

int Num ( ListNode *f );

float Av( ListNode *f,  int& n );

public:

List ( ) : first(NULL), current (NULL) { }//構造函數

~List ( ){ }//析構函數

ListNode* NewNode ( const int item );//創建鏈表結點其值為item

void NewList const int retvalue );//建立鏈表, 以輸入retvalue結束

void PrintList ( );//輸出鏈表所有結點數據

int GetMax ( ) { return Max ( first ); }//求鏈表所有數據的最大值

int GetNum ( ) { return Num first ); }//求鏈表中數據個數

float GetAvg ( ) { return Avg ( first ); }//求鏈表所有數據的平均值

};

 

ListNode* List :: NewNode ( const int item ) {//創建新鏈表結點

ListNode *newnode = new ListNode (item);

return newnode;

}

 

void List :: NewList ( const int retvalue ) {//建立鏈表以輸入retvalue結束

first = NULL;  int value;  ListNode *q;

cout << "Input your data:\n";//提示

cin >> value;//輸入

while ( value != retvalue ) {//輸入有效

q = NewNode ( value );//建立包含value的新結點

if ( first == NULL ) first = current = q;//空表時新結點成為鏈表第一個結點

else { current->link = q;  current = q; }//非空表時新結點鏈入鏈尾

cin >> value;//再輸入

}

current->link = NULL;//鏈尾封閉

}

void List :: PrintList ( ) {//輸出鏈表

cout << "\nThe List is : \n";

ListNode *p = first;

while ( p != NULL ) { cout << p->data << '  ';  p = p->link; }

cout << ‘\n’; 

}

int List :: Max ( ListNode *f ) {//遞歸算法 : 求鏈表中的最大值

if ( f ->link == NULL ) return f ->data;//遞歸結束條件

int temp = Max ( f ->link );//在當前結點的后繼鏈表中求最大值

if ( f ->data > temp ) return f ->data;//如果當前結點的值還要大返回當前檢點值

else return temp;//否則返回后繼鏈表中的最大值

}

 

int List :: Num ( ListNode *f ) {//遞歸算法 : 求鏈表中結點個數

if ( f == NULL ) return 0;//空表返回0

return 1+ Num ( f ->link );//否則返回后繼鏈表結點個數加1

}

float List :: Avg ( ListNode *f int& n ) {//遞歸算法 求鏈表中所有元素的平均值

if ( ->link == NULL ) //鏈表中只有一個結點, 遞歸結束條件

{ n = 1;  return ( float ) (f ->data ); }

else { float Sum = Avg ( f ->linkn ) * n;  n++;  return ( f ->data + Sum ) / n; }

}

#include "RecurveList.h"//定義在主文件中

int main ( int argccharargv[ ] ) {

List test;   int finished;

cout << “輸入建表結束標志數據 :;

cin >> finished;//輸入建表結束標志數據 

test.NewList finished );//建立鏈表

test.PrintList ( );//打印鏈表

cout << "\nThe Max is : " << test.GetMax ( );

cout << "\nThe Num is : " << test.GetNum ( );

cout << "\nThe Ave is : " << test.GetAve () << '\n';

printf ( "Hello World!\n" );

return 0;

}


4章  串、數組和廣義表

習題

1選擇題

1串是一種特殊的線性表,其特殊性體現在(  )。

  A.可以順序存儲               B數據元素是一個字符      

C.可以鏈式存儲               D.數據元素可以是多個字符若 

2串下面關於串的的敘述中,(  )是不正確的? 

A.串是字符的有限序列          B.空串是由空格構成的串

C.模式匹配是串的一種重要運算  D.串既可以采用順序存儲,也可以采用鏈式存儲

3)串“ababaaababaa”的next數組為(  )。

A.012345678999   B.012121111212   C.011234223456    D.0123012322345

4)串“ababaabab”的nextval為(  )。

A010104101      B.010102101      C.010100011       D.010101011 

5)串的長度是指(  )。

A.串中所含不同字母的個數       B.串中所含字符的個數

C.串中所含不同字符的個數       D.串中所含非空格字符的個數

6)假設以行序為主序存儲二維數組A=array[1..100,1..100],設每個數據元素占2個存儲單元,基地址為10,則LOC[5,5]=(  )。

A.808            B818             C.1010             D.1020

7)設有數組A[i,j],數組的每個元素長度為3字節,i的值為1到8,j的值為1到10,數組從內存首地址BA開始順序存放,當用以列為主存放時,元素A[5,8]的存儲首地址為(  )。

A.BA+141        BBA+180         C.BA+222         D.BA+225

8)設有一個10階的對稱矩陣A,采用壓縮存儲方式,以行序為主存儲,a11為第一元素,其存儲地址為1,每個元素占一個地址空間,則a85的地址為(  )。

A.13             B33              C.18               D.40

9)若對n階對稱矩陣A以行序為主序方式將其下三角形的元素(包括主對角線上所有元素)依次存放於一維數組B[1..(n(n+1))/2]中,則在B中確定aij(i<j)的位置k的關系為(  )。

A.i*(i-1)/2+j      Bj*(j-1)/2+i      C.i*(i+1)/2+j      D.j*(j+1)/2+i

10A[N,N]是對稱矩陣,將下面三角(包括對角線)以行序存儲到一維數組T[N(N+1)/2]中,則對任一上三角元素a[i][j]對應T[k]的下標k是(  )。

A.i(i-1)/2+j       Bj(j-1)/2+i       C.i(j-i)/2+1       D.j(i-1)/2+1

11)設二維數組A[1.. m,1.. n](即m行n列)按行存儲在數組B[1.. m*n]中,則二維數組元素A[i,j]在一維數組B中的下標為(  )。

A.(i-1)*n+j       B.(i-1)*n+j-1      C.i*(j-1)         D.j*m+i-1

12)數組A[0..4,-1..-3,5..7]中含有元素的個數(  )。

A.55            B45             C.36            D.16

13)廣義表A=(a,b,(c,d),(e,(f,g))),則Head(Tail(Head(Tail(Tail(A)))))的值為(  )。

A.(g)            B.(d)             C.c            Dd

14)廣義表((a,b,c,d))的表頭是(  ),表尾是(  )。

A.a              B.( )             C.(a,b,c,d)      D.(b,c,d)

15)設廣義表L=((a,b,c)),則L的長度和深度分別為(  )。

A.1和1          B.1和3          C12          D.2和3 

 

1已知模式串t=‘abcaabbabcab’寫出用KMP法求得的每個字符對應的next和nextval函數值。

 

模式串tnextnextval值如下:

j

1  2  3  4  5  6  7  8  9  10 11 12 

t

a  b  c  a  a  b  b  a  b   c  a  b

next[j]

0  1  1  1  2  2  3  1  2  3  4  5

nextval[j]

0  1  1  0  2  1  3  0  1  1  0  5

 

2設目標為t=“abcaabbabcabaacbacba”,模式為p=“abcabaa”

① 計算模式p的naxtval函數值;

② 不寫出算法,只畫出利用KMP算法進行模式匹配時每一趟的匹配過程。

 

 pnextval函數值為0110132。(pnext函數值為0111232)。

 利用KMP(改進的nextval)算法,每趟匹配過程如下:

  第一趟匹配: abcaabbabcabaacbacba

               abcab(i=5,j=5)

  第二趟匹配: abcaabbabcabaacbacba

                   abc(i=7,j=3)

  第三趟匹配: abcaabbabcabaacbacba

                     a(i=7,j=1)

  第四趟匹配: abcaabbabcabaac bacba

   (成功)             abcabaa(i=15,j=8)

 

3數組A中,每個元素A[i,j]的長度均為32個二進位,行下標從-1到9,列下標從1到11,從首地址S開始連續存放主存儲器中,主存儲器字長為16位。求:

① 存放該數組所需多少單元?

② 存放數組第4列所有元素至少需多少單元?

③ 數組按行存放時,元素A[7,4]的起始地址是多少?

④ 數組按列存放時,元素A[4,7]的起始地址是多少?

每個元素32個二進制位,主存字長16位,故每個元素占2個字長,行下標可平移至111

1242   222   3s+182   4s+142

 

(4)請將香蕉banana用工具 H( )—Head( ),T( )—Tail( )從L中取出。

L=(apple,(orange,(strawberry,(banana)),peach),pear)

HHTHTHTL)))))))

 

 

5寫一個算法統計在輸入字符串中各個不同字符出現的頻度並將結果存入文件(字符串中的合法字符為A-Z這26個字母和0-9這10個數字)。

 

void Count()

//統計輸入字符串中數字字符和字母字符的個數。

int inum[36]

char ch

fori0i<36i++num[i]=0;// 初始化

while((chgetchar())!=#’)  //#’表示輸入字符串結束。

if(‘0<=ch<=9’){i=ch48;num[i]++;}        // 數字字符

  elseif(‘A<=ch<=Z’){i=ch-65+10;num[i]++;}// 字母字符

fori=0i<10i++  // 輸出數字字符的個數

printf(“數字%d的個數=%d\n”,inum[i]);

fori10i<36i++// 求出字母字符的個數

printf(“字母字符%c的個數=%d\n”,i55num[i]);

// 算法結束。

6寫一個遞歸算法來實現字符串逆序存儲,要求不另設串存儲空間。

[題目分析]實現字符串的逆置並不難,但本題“要求不另設串存儲空間”來實現字符串逆序存儲,即第一個輸入的字符最后存儲,最后輸入的字符先存儲,使用遞歸可容易做到。

 void InvertStore(char A[])

//字符串逆序存儲的遞歸算法。

char ch;

static int i = 0;//需要使用靜態變量

scanf ("%c",&ch);

if (ch!= '.')    //規定'.'是字符串輸入結束標志

{InvertStore(A);

 A[i++] = ch;//字符串逆序存儲

}

A[i] = '\0';  //字符串結尾標記

}//結束算法InvertStore

 

7編寫算法,實現下面函數的功能。函數void insert(char*s,char*t,int pos)將字符串t插入到字符串s中,插入位置為pos。假設分配給字符串s的空間足夠讓字符串t插入。(說明:不得使用任何庫函數)

[題目分析]本題是字符串的插入問題,要求在字符串spos位置,插入字符串t。首先應查找字符串spos位置,將第pos個字符到字符串s尾的子串向后移動字符串t的長度,然后將字符串t復制到字符串s的第pos位置后。

  對插入位置pos要驗證其合法性,小於1或大於串s的長度均為非法,因題目假設給字符串s的空間足夠大,故對插入不必判溢出。

void  insert(char *s,char *t,int pos)

//將字符串t插入字符串s的第pos個位置。

{int i=1,x=0;  char *p=s,*q=t;  //pq分別為字符串st的工作指針

 if(pos<1) {printf(“pos參數位置非法\n”);exit(0);}

while(*p!=’\0’&&i<pos) {p++;i++;} //pos位置

    //pos小於串s長度,則查到pos位置時,i=pos

 if(*p == '/0') {printf("%d位置大於字符串s的長度",pos);exit(0);}

 else      //查找字符串的尾

   while(*p!= '/0') {p++; i++;}  //查到尾時,i為字符‘\0’的下標,p也指向‘\0’。

 while(*q!= '\0') {q++; x++; }   //查找字符串t的長度x,循環結束時q指向'\0'

 for(j=i;j>=pos ;j--){*(p+x)=*p; p--;}//spos后的子串右移,空出串t的位置。

 q--;  //指針q回退到串t的最后一個字符

 for(j=1;j<=x;j++) *p--=*q--;  //t串插入到spos位置上

 [算法討論s的結束標記('\0')也后移了,而串t的結尾標記不應插入到s中。

 

8已知字符串S1中存放一段英文,寫出算法format(s1,s2,s3,n),將其按給定的長度n格式化成兩端對齊的字符串S2, 其多余的字符送S3。

[題目分析]本題要求字符串s1拆分成字符串s2和字符串s3,要求字符串s2“按給定長度n格式化成兩端對齊的字符串”,即長度為n且首尾字符不得為空格字符。算法從左到右掃描字符串s1,找到第一個非空格字符,計數到n,第n個拷入字符串s2的字符不得為空格,然后將余下字符復制到字符串s3中。

void format (char *s1,*s2,*s3)

//將字符串s1拆分成字符串s2和字符串s3,要求字符串s2是長n且兩端對齊

{char *p=s1, *q=s2;

int i=0;

while(*p!= '\0' && *p== ' ') p++;//濾掉s1左端空格

if(*p== '\0') {printf("字符串s1為空串或空格串\n");exit(0);}

while( *p!='\0' && i<n){*q=*p; q++; p++; i++;}//字符串s1向字符串s2中復制

if(*p =='\0'){ printf("字符串s1沒有%d個有效字符\n",n); exit(0);}

if(*(--q)==' ' ) //若最后一個字符為空格,則需向后找到第一個非空格字符

 {p-- ;          //p指針也后退

  while(*p==' '&&*p!='\0') p++;//往后查找一個非空格字符作串s2的尾字符

  if(*p=='\0') {printf("s1串沒有%d個兩端對齊的字符串\n",n); exit(0);}

  *q=*p;         //字符串s2最后一個非空字符

  *(++q)='\0';   //s2字符串結束標記

  }

*q=s3;p++;      //s1串其余部分送字符串s3

while (*p!= '\0') {*q=*p; q++; p++;}

*q='\0';        //置串s3結束標記

}

 

 

(9)設二維數組a[1..m, 1..n] 含有m*n 個整數。

① 寫一個算法判斷a中所有元素是否互不相同?輸出相關信息(yes/no);

② 試分析算法的時間復雜度。

[題目分析]判斷二維數組中元素是否互不相同,只有逐個比較,找到一對相等的元素,就可結論為不是互不相同。如何達到每個元素同其它元素比較一次且只一次?在當前行,每個元素要同本行后面的元素比較一次(下面第一個循環控制變量pfor循環),然后同第i+1行及以后各行元素比較一次,這就是循環控制變量kp的二層for循環。

int JudgEqual(ing a[m][n],int m,n)  

 //判斷二維數組中所有元素是否互不相同,如是,返回1;否則,返回0

{for(i=0;i<m;i++)

  for(j=0;j<n-1;j++)

   { for(p=j+1;p<n;p++) //和同行其它元素比較

      if(a[i][j]==a[i][p]) {printf(“no”); return(0); }

  //只要有一個相同的,就結論不是互不相同

     for(k=i+1;k<m;k++)  //和第i+1行及以后元素比較

      for(p=0;p<n;p++)

         if(a[i][j]==a[k][p]) {printf(“no”); return(0); }             

}// for(j=0;j<n-1;j++)

printf(yes”); return(1);   //元素互不相同

}//算法JudgEqual結束

2)二維數組中的每一個元素同其它元素都比較一次,數組中共m*n個元素,第1個元素同其它m*n-1個元素比較,第2個元素同其它m*n-2 個元素比較,……,第m*n-1個元素同最后一個元素(m*n)比較一次,所以在元素互不相等時總的比較次數為 (m*n-1)+(m*n-2)++2+1=m*n(m*n-1)/2。在有相同元素時,可能第一次比較就相同,也可能最后一次比較時相同,設在(m*n-1)個位置上均可能相同,這時的平均比較次數約為(m*n(m*n-1)/4,總的時間復雜度是O(n4)

(10)設任意n個整數存放於數組A(1:n)中,試編寫算法,將所有正數排在所有負數前面(要求算法復雜性為0(n))。 

[題目分析]本題屬於排序問題,只是排出正負,不排出大小。可在數組首尾設兩個指針iji自小至大搜索到負數停止,j自大至小搜索到正數停止。然后ij所指數據交換,繼續以上過程,直到 i=j為止。

void Arrange(int A[],int n) 

//n個整數存於數組A中,本算法將數組中所有正數排在所有負數的前面

 {int i=0,j=n-1,x;  //用類C編寫,數組下標從0開始

  while(i<j)

{while(i<j && A[i]>0)  i++;

while(i<j && A[j]<0)  j--;

  if(i<j) {x=A[i]; A[i++]=A[j]; A[j--]=x; }//交換A[i] A[j]

}

     }//算法Arrange結束.

[算法討論]對數組中元素各比較一次,比較次數為n。最佳情況(已排好,正數在前,負數在后)不發生交換,最差情況(負數均在正數前面)發生n/2次交換。用類c編寫,數組界偶是0..n-1。空間復雜度為O(1).


5章  樹和二叉樹

1.選擇題

1)把一棵樹轉換為二叉樹后,這棵二叉樹的形態是(   )。              

A.唯一的                          B.有多種

C.有多種,但根結點都沒有左孩子    D.有多種,但根結點都沒有右孩子

2由3 個結點可以構造出多少種不同的二叉樹?(    )

A.2          B.3             C.4          D5   

3一棵完全二叉樹上有1001個結點,其中葉子結點的個數是(  )。

A.250         B. 500          C.254        D501   

4一個具有1025個結點的二叉樹的高h為(  )。

A.11          B.10             C111025之間       D.10至1024之間

5深度為h的滿m叉樹的第k層有(  )個結點。(1=<k=<h) 

Amk-1          B.mk-1            C.mh-1        D.mh-1

6利用二叉鏈表存儲樹,則根結點的右指針是(  )。

A.指向最左孩子        B.指向最右孩子         C.空        D.非空

7對二叉樹的結點從1開始進行連續編號,要求每個結點的編號大於其左、右孩子的編號,同一結點的左右孩子中,其左孩子的編號小於其右孩子的編號,可采用(  )遍歷實現編號。

A.先序         B. 中序           C. 后序       D. 從根開始按層次遍歷

8若二叉樹采用二叉鏈表存儲結構,要交換其所有分支結點左、右子樹的位置,利用(  )遍歷方法最合適。

A.前序         B.中序            C.后序      D.按層次

9在下列存儲形式中,(  )不是樹的存儲形式?

A.雙親表示法   B.孩子鏈表表示法   C.孩子兄弟表示法   D.順序存儲表示法

10一棵非空的二叉樹的先序遍歷序列與后序遍歷序列正好相反,則該二叉樹一定滿足(  )。

A.所有的結點均無左孩子        B.所有的結點均無右孩子

C.只有一個葉子結點            D.是任意一棵二叉樹

11某二叉樹的前序序列和后序序列正好相反,則該二叉樹一定是(  )的二叉樹。

A.空或只有一個結點            B.任一結點無左子樹  

C.高度等於其結點數            D.任一結點無右子樹

12若X是二叉中序線索樹中一個有左孩子的結點,且X不為根,則X的前驅為(  )。

A.X的雙親                      B.X的右子樹中最左的結點 

CX的左子樹中最右結點          D.X的左子樹中最右葉結點

13引入二叉線索樹的目的是(  )。

A.加快查找結點的前驅或后繼的速度    B.為了能在二叉樹中方便的進行插入與刪除

C.為了能方便的找到雙親              D.使二叉樹的遍歷結果唯一

14線索二叉樹是一種(  )結構。

A.邏輯         B. 邏輯和存儲          C物理            D.線性

15設F是一個森林,B是由F變換得的二叉樹。若F中有n個非終端結點,則B中右指針域為空的結點有(  )個。

A. n-1          B.n                    C n+1           D. n+2

 

2.應用題

(1)試找出滿足下列條件的二叉樹

① 先序序列與后序序列相同    ②中序序列與后序序列相同

③ 先序序列與中序序列相同    ④中序序列與層次遍歷序列相同

先序遍歷二叉樹的順序是“根—左子樹—右子樹”,中序遍歷“左子樹—根—右子樹”,后序遍歷順序是:“左子樹—右子樹―根",根據以上原則,本題解答如下:

(1)  若先序序列與后序序列相同,則或為空樹,或為只有根結點的二叉樹

(2)  若中序序列與后序序列相同,則或為空樹,或為任一結點至多只有左子樹的二叉樹.

(3)  若先序序列與中序序列相同,則或為空樹,或為任一結點至多只有右子樹的二叉樹.

(4)  若中序序列與層次遍歷序列相同,則或為空樹,或為任一結點至多只有右子樹的二叉樹

 

(2)設一棵二叉樹的先序序列: A B D F C E G H ,中序序列: B F D A G E H C

①畫出這棵二叉樹。

②畫出這棵二叉樹的后序線索樹。

③將這棵二叉樹轉換成對應的樹(或森林)。

 

 

 

 

 

 

 

 

 

 

        (1)                           (2)

(3) 假設用於通信的電文僅由8個字母組成,字母在電文中出現的頻率分別為0.070.190.020.060.320.030.210.10

① 試為這8個字母設計赫夫曼編碼。

② 試設計另一種由二進制表示的等長編碼方案。

③ 對於上述實例,比較兩種方案的優缺點。

解:方案1;哈夫曼編碼

先將概率放大100倍,以方便構造哈夫曼樹。

 w={7,19,2,6,32,3,21,10},按哈夫曼規則:【[2,3),6], (7,10), ……19, 21, 32

 

100

40)      (60

19     21     32   28

(17) (11

   7     10  6    5

                 2     3

 

 

方案比較:

 

 

 

 

 

 

 

 

 

方案1WPL2(0.19+0.32+0.21)+4(0.07+0.06+0.10)+5(0.02+0.03)=1.44+0.92+0.25=2.61

方案2WPL3(0.19+0.32+0.21+0.07+0.06+0.10+0.02+0.03)=3

結論:哈夫曼編碼優於等長二進制編碼

 

 

 (4)已知下列字符A、B、C、D、E、F、G的權值分別為3、12、7、4、2、8,11,試填寫出其對應哈夫曼樹HT的存儲結構的初態和終態。

 

初態:

 

weight

parent

lchild

rchild

1

3

0

0

0

2

12

0

0

0

3

7

0

0

0

4

4

0

0

0

5

2

0

0

0

6

8

0

0

0

7

11

0

0

0

8

 

0

0

0

9

 

0

0

0

10

 

0

0

0

11

 

0

0

0

12

 

0

0

0

13

 

0

0

0

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

weight

parent

lchild

rchild

1

3

8

0

0

2

12

12

0

0

3

7

10

0

0

4

4

9

0

0

5

2

8

0

0

6

8

10

0

0

7

11

11

0

0

8

5

9

5

1

9

9

11

4

8

10

15

12

3

6

11

20

13

9

7

12

27

13

2

10

13

47

0

11

12

終態

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

3.算法設計題

以二叉鏈表作為二叉樹的存儲結構,編寫以下算法:

(1)統計二叉樹的葉結點個數。

int LeafNodeCount(BiTree T)

{

if(T==NULL)

return 0;                                //如果是空樹,則葉子結點個數為0

else if(T->lchild==NULL&&T->rchild==NULL)

return 1;                                //判斷該結點是否是葉子結點(左孩子右孩子都為空),若是則返回1

else

return LeafNodeCount(T->lchild)+LeafNodeCount(T->rchild);

}

(2)判別兩棵樹是否相等。

(3)交換二叉樹每個結點的左孩子和右孩子。

void ChangeLR(BiTree &T)

{

BiTree temp;

if(T->lchild==NULL&&T->rchild==NULL)

return;

else

{

temp = T->lchild;

T->lchild = T->rchild;

T->rchild = temp;

}

ChangeLR(T->lchild);

ChangeLR(T->rchild);

}

(4)設計二叉樹的雙序遍歷算法(雙序遍歷是指對於二叉樹的每一個結點來說,先訪問這個結點,再按雙序遍歷它的左子樹,然后再一次訪問這個結點,接下來按雙序遍歷它的右子樹)。

void DoubleTraverse(BiTree T)

{

if(T == NULL)

return;

else if(T->lchild==NULL&&T->rchild==NULL)

cout<<T->data;

else

{

cout<<T->data;

DoubleTraverse(T->lchild);

cout<<T->data;

DoubleTraverse(T->rchild);

}

}

(5)計算二叉樹最大的寬度(二叉樹的最大寬度是指二叉樹所有層中結點個數的最大值)。

[題目分析求二叉樹高度的算法見上題。求最大寬度可采用層次遍歷的方法,記下各層結點數,每層遍歷完畢,若結點數大於原先最大寬度,則修改最大寬度。

int Width(BiTree bt)//求二叉樹bt的最大寬度

{if (bt==null) return (0);  //空二叉樹寬度為0

else 

{BiTree Q[];//Q是隊列,元素為二叉樹結點指針,容量足夠大

  front=1;rear=1;last=1;//front隊頭指針,rear隊尾指針,last同層最右結點在隊列中的位置

  temp=0; maxw=0;       //temp記局部寬度, maxw記最大寬度

  Q[rear]=bt;           //根結點入隊列

  while(front<=last)

  {p=Q[front++]; temp++; //同層元素數加1

   if (p->lchild!=null)  Q[++rear]=p->lchild;   //左子女入隊

if (p->rchild!=null)  Q[++rear]=p->rchild;   //右子女入隊

   if (front>last)      //一層結束, 

    {last=rear;

if(temp>maxw) maxw=temp;//last指向下層最右元素更新當前最大寬度

     temp=0;

 }//if    

}//while

  return (maxw);

}//結束width

 

(6)用按層次順序遍歷二叉樹的方法,統計樹中具有度為1的結點數目。

int Level(BiTree bt) //層次遍歷二叉樹,並統計度為1的結點的個數

{int num=0; //num統計度為1的結點的個數

     if(bt){QueueInit(Q); QueueIn(Q,bt)//Q是以二叉樹結點指針為元素的隊列

     while(!QueueEmpty(Q))

{p=QueueOut(Q); printf(p->data);     //出隊,訪問結點

if(p->lchild && !p->rchild ||!p->lchild && p->rchild)num++;//度為1的結點

if(p->lchild) QueueIn(Q,p->lchild); //非空左子女入隊

if(p->rchild) QueueIn(Q,p->rchild); //非空右子女入隊

}  }//if(bt)         

return(num); }//返回度為1的結點的個數 

 

(7)求任意二叉樹中第一條最長的路徑長度,並輸出此路徑上各結點的值。

[題目分析]因為后序遍歷棧中保留當前結點的祖先的信息,用一變量保存棧的最高棧頂指針,每當退棧時,棧頂指針高於保存最高棧頂指針的值時,則將該棧倒入輔助棧中,輔助棧始終保存最長路徑長度上的結點,直至后序遍歷完畢,則輔助棧中內容即為所求。

void LongestPath(BiTree bt)//求二叉樹中的第一條最長路徑長度

{BiTree p=bt,l[],s[]; //l, s是棧,元素是二叉樹結點指針,l中保留當前最長路徑中的結點

  int itop=0,tag[],longest=0;

  while(p || top>0)

   { while(p) {s[++top]=ptag[top]=0; p=p->Lc;} //沿左分枝向下

 if(tag[top]==1)    //當前結點的右分枝已遍歷

  {if(!s[top]->Lc && !s[top]->Rc)  //只有到葉子結點時,才查看路徑長度

if(top>longest) {for(i=1;i<=top;i++) l[i]=s[i]; longest=top; top--;}

//保留當前最長路徑到l棧,記住最高棧頂指針,退棧

}

     else if(top>0) {tag[top]=1; p=s[top].Rc;}   //沿右子分枝向下

   }//while(p!=null||top>0)

}//結束LongestPath

 

(8)輸出二叉樹中從每個葉子結點到根結點的路徑。

[題目分析]采用先序遍歷的遞歸方法,當找到葉子結點*b時,由於*b葉子結點尚未添加到path中,因此在輸出路徑時還需輸出b->data值。對應的遞歸算法如下:

void AllPath(BTNode *b,ElemType path[],int pathlen)

{

    int i;

    if (b!=NULL)

    {

        if (b->lchild==NULL && b->rchild==NULL) //*b為葉子結點

        {

            cout << " " << b->data << "到根結點路徑:" << b->data;

            for (i=pathlen-1;i>=0;i--)

        cout << endl;

    }

    else

    {

        path[pathlen]=b->data;           //將當前結點放入路徑中

        pathlen++;                       //路徑長度增1

        AllPath(b->lchild,path,pathlen); //遞歸掃描左子樹

        AllPath(b->rchild,path,pathlen); //遞歸掃描右子樹

        pathlen--;                       //恢復環境

    }

  }

}


第6章 圖

 

1.選擇題

1在一個圖中,所有頂點的度數之和等於圖的邊數的(   )倍。

  A.1/2            B.1             C2             D.4 

2在一個有向圖中,所有頂點的入度之和等於所有頂點的出度之和的(   )倍。

  A1/2            B1             C.2             D.4 

3具有n個頂點的有向圖最多有(   )條邊。  

An              Bn(n-1)         C.n(n+1)        D.n2 

4n個頂點的連通圖用鄰接距陣表示時,該距陣至少有(   )個非零元素。

An              B2(n-1)         C.n/2           D.n2 

5G是一個非連通無向圖,共有28條邊,則該圖至少有(   )個頂點。

A7              B.8             C9             D.10 

6若從無向圖的任意一個頂點出發進行一次深度優先搜索可以訪問圖中所有的頂點,則該圖一定是(   )圖。

A非連通         B連通          C.強連通        D.有向

7下面( )算法適合構造一個稠密圖G的最小生成樹。

A Prim算法      BKruskal法   C.Floyd算法     D.Dijkstra算法

8用鄰接表表示圖進行廣度優先遍歷時,通常借助(   )來實現算法。

A.棧            B. 隊列            C.  樹            D.圖 

9用鄰接表表示圖進行深度優先遍歷時,通常借助(   )來實現算法。

A.棧            B. 隊列            C.  樹            D.圖 

10深度優先遍歷類似於二叉樹的(   )。

A.先序遍歷      B.中序遍歷        C.后序遍歷      D.層次遍歷

11廣度優先遍歷類似於二叉樹的(   )。

A.先序遍歷      B.中序遍歷        C.后序遍歷       D層次遍歷

12圖的BFS生成樹的樹高比DFS生成樹的樹高(   )。

A.小            B.相等            C小或相等       D.大或相等

13 已知圖的鄰接矩陣如圖 6.25 所示,則從頂點 0 出發按深度優先遍歷的結果是(   )。

6.25  鄰接矩陣

14已知圖的鄰接表如圖6.26所示,則從頂點0出發按廣度優先遍歷的結果是(   ),按深度優先遍歷的結果是(   )。

 

6.26  鄰接表

15下面(   )方法可以判斷出一個有向圖是否有環。

A深度優先遍歷      B拓撲排序      C.求最短路徑     D.求關鍵路徑

2.應用題

1)已知如圖6.27所示的有向圖,請給出:

① 每個頂點的入度和出度;    

② 鄰接矩陣;

③ 鄰接表;

④ 逆鄰接表。             

 

 

                      

 

 

 

 


2)已知如圖6.28所示的無向網,請給出:

① 鄰接矩陣;    

② 鄰接表;

③ 最小生成樹

            

 

 

            

                                      

 

  

 

 

 

 

 

a

b

4

c

3

 

 

 

 

 

 

 

 

 

 

 

 

b

a

4

c

5

d

5

e

9

^

 

 

 

 

 

c

a

3

b

5

d

5

h

5

^

 

 

 

 

 

d

b

5

c

5

e

7

f

6

g

5

h

4^

e

b

9

d

7

f

3

^

 

 

 

 

 

 

 

 

f

d

6

e

3

g

2

^

 

 

 

 

 

 

 

 

g

d

5

f

2

h

6

^

 

 

 

 

 

 

 

 

h

c

5

d

4

g

6

^

 

 

 

 

 

 

 

 

 

 

 

3已知圖的鄰接矩陣如6.29所示。試分別畫出自頂點1出發進行遍歷所得的深度優先生成樹和廣度優先生成樹。

 

 

 

 

4)有向網如圖6.30所示,試用迪傑斯特拉算法求出從頂點a到其他各頂點間的最短路徑,完成表6.9  

 

 

 

 

 

 

D

終點

i=1

i=2

i=3

i=4

i=5

i=6

b

15

(a,b)

15

(a,b)

15

(a,b)

15

(a,b)

15

(a,b)

15

(a,b)

c

2

(a,c)

 

 

 

 

 

d

12

(a,d)

12

(a,d)

11

(a,c,f,d)

11

(a,c,f,d)

 

 

e

 

10

(a,c,e)

10

(a,c,e)

 

 

 

f

 

6

(a,c,f)

 

 

 

 

g

 

 

16

(a,c,f,g)

16

(a,c,f,g)

14

(a,c,f,d,g)

 

S

終點集

{a,c}

{a,c,f}

{a,c,f,e}

{a,c,f,e,d}

{a,c,f,e,d,g}

{a,c,f,e,d,g,b}

    

5試對圖6.31所示的AOE-網:

① 求這個工程最早可能在什么時間結束;    

② 求每個活動的最早開始時間和最遲開始時間;

③ 確定哪些活動是關鍵活動

 

 

【解答】按拓撲有序的順序計算各個頂點的最早可能開始時間Ve和最遲允許開始時間Vl。然后再計算各個活動的最早可能開始時間e和最遲允許開始時間l,根據l - e = 0? 來確定關鍵活動,從而確定關鍵路徑。

 

 

 1  

 2 ¸

 3 ·

 4 ¹

 5 º

 6 »

 Ve

 0

 19

 15

 29

 38 

 43

 Vl

 0

 19

 15

 37

 38

 43

 

 

<1, 2>

<1, 3>

<3, 2>

<2, 4>

<2, 5>

<3, 5>

<4, 6>

<5, 6>

 e

  0

  0

 15

 19

 19

 15

 29

 38

 l

 17

  0

 15

 27

 19

 27

 37

 38

l-e

 17

  0

  0

  8

  0

 12

  8

  0

此工程最早完成時間為43。關鍵路徑為<1, 3><3, 2><2, 5><5, 6>

      

3.算法設計題

(1)分別以鄰接矩陣和鄰接表作為存儲結構,實現以下圖的基本操作:

① 增添一個新頂點v,InsertVex(G, v);

② 刪除頂點v及其相關的邊,DeleteVex(G, v);

③ 增加一條邊<v,w>InsertArc(G, v, w);

④ 刪除一條邊<v,w>DeleteArc(G, v, w)。

//本題中的圖G均為有向無權圖,其余情況容易由此寫出
Status Insert_Vex(MGraph &G, char v)//在鄰接矩陣表示的圖G上插入頂點v
{
if(G.vexnum+1)>MAX_VERTEX_NUM return INFEASIBLE;
G.vexs[++G.vexnum]=v;
return OK;
}//Insert_Vex
Status Insert_Arc(MGraph &G,char v,char w)//在鄰接矩陣表示的圖G上插入邊(v,w)
{
if((i=LocateVex(G,v))<0) return ERROR;
if((j=LocateVex(G,w))<0) return ERROR;
if(i==j) return ERROR;
if(!G.arcs[j].adj)
{
G.arcs[j].adj=1;
G.arcnum++;
}
return OK;
}//Insert_Arc
Status Delete_Vex(MGraph &G,char v)//在鄰接矩陣表示的圖G上刪除頂點v
{
n=G.vexnum;
if((m=LocateVex(G,v))<0) return ERROR;
G.vexs[m]<->G.vexs[n]; //將待刪除頂點交換到最后一個頂點
for(i=0;i<n;i++)
{
G.arcs[m]=G.arcs[n];
G.arcs[m]=G.arcs[n]; //將邊的關系隨之交換
}
G.arcs[m][m].adj=0;
G.vexnum--;
return OK;
}//Delete_Vex
分析:如果不把待刪除頂點交換到最后一個頂點的話,算法將會比較復雜,而伴隨着大量元素的移動,時間復雜度也會大大增加.
Status Delete_Arc(MGraph &G,char v,char w)//在鄰接矩陣表示的圖G上刪除邊(v,w)
{
if((i=LocateVex(G,v))<0) return ERROR;
if((j=LocateVex(G,w))<0) return ERROR;
if(G.arcs[j].adj)
{
G.arcs[j].adj=0;
G.arcnum--;
}
return OK;
}//Delete_Arc
//為節省篇幅,本題只給出Insert_Arc算法.其余算法請自行寫出.
Status Insert_Arc(ALGraph &G,char v,char w)//在鄰接表表示的圖G上插入邊(v,w)
{
if((i=LocateVex(G,v))<0) return ERROR;
if((j=LocateVex(G,w))<0) return ERROR;
p=(ArcNode*)malloc(sizeof(ArcNode));
p->adjvex=j;p->nextarc=NULL;
if(!G.vertices.firstarc) G.vertices.firstarc=p;
else
{
for(q=G.vertices.firstarc;q->q->nextarc;q=q->nextarc)
if(q->adjvex==j) return ERROR; //邊已經存在
q->nextarc=p;
}
G.arcnum++;
return OK;
}//Insert_Arc

(2)一個連通圖采用鄰接表作為存儲結構,設計一個算法,實現從頂點v出發的深度優先遍歷的非遞歸過程。

數據結構考研指導2327.3.7

(3)設計一個算法,求圖G中距離頂點v的最短路徑長度最大的一個頂點,設v可達其余各個頂點。

數據結構考研指導2327.3.8

(4)試基於圖的深度優先搜索策略寫一算法,判別以鄰接表方式存儲的有向圖中是否存在由頂點vi到頂點vj的路徑(i≠j)。

1

int visited[MAXSIZE]; //指示頂點是否在當前路徑上 

int exist_path_DFS(ALGraph G,int i,int j)//深度優先判斷有向圖G中頂點i到頂點

是否有路徑,是則返回1,否則返回

  if(i==j) return 1; //i就是

  else 

  { 

    visited[i]=1; 

    for(p=G.vertices[i].firstarc;p;p=p->nextarc) 

    { 

      k=p->adjvex; 

      if(!visited[k]&&exist_path(k,j)) return 1;//i下游的頂點到j有路徑 

    }//for 

  }//else 

}//exist_path_DFS 

 

2:(以上算法似乎有問題:如果不存在路徑,則原程序不能返回0。我的解決方式是在原程序的中引入一變量level來控制遞歸進行的層數。具體的方法我在程序中用紅色標記出來了。)

int visited[MAXSIZE]; //指示頂點是否在當前路徑上 

int level1;//遞歸進行的層數

int exist_path_DFS(ALGraph G,int i,int j)//深度優先判斷有向圖G中頂點i到頂點

是否有路徑,是則返回1,否則返回

  if(i==j) return 1; //i就是

  else 

  { 

    visited[i]=1; 

    for(p=G.vertices[i].firstarc;p;p=p->nextarclevel--) 

    { level++;

      k=p->adjvex; 

      if(!visited[k]&&exist_path(k,j)) return 1;//i下游的頂點到j有路徑 

}//for 

  }//else 

if (level==1)  return 0;

}//exist_path_DFS 

(5)采用鄰接表存儲結構,編寫一個算法,判別無向圖中任意給定的兩個頂點之間是否存在一條長度為為k的簡單路徑。      

(注1:一條路徑為簡單路徑指的是其頂點序列中不含有重現的頂點。

2:此題可參見嚴題集P207-208中有關按“路徑”遍歷的算法基本框架。)

int visited[MAXSIZE]; 

int exist_path_len(ALGraph G,int i,int j,int k)//判斷鄰接表方式存儲的有向圖

的頂點ij是否存在長度為k的簡單路徑 

  if(i==j&&k==0) return 1; //找到了一條路徑,且長度符合要求 

  else if(k>0) 

  { 

    visited[i]=1; 

    for(p=G.vertices[i].firstarc;p;p=p->nextarc) 

    { 

      l=p->adjvex; 

      if(!visited[l]) 

        if(exist_path_len(G,l,j,k-1)) return 1; //剩余路徑長度減一 

    }//for 

    visited[i]=0; //本題允許曾經被訪問過的結點出現在另一條路徑中 

  }//else 

  return 0; //沒找到 

}//exist_path_len

 


7章  查找

 

1.選擇題

1對n個元素的表做順序查找時,若查找每個元素的概率相同,則平均查找長度為(   )。

A(n-1)/2       B. n/2        C(n+1)/2        D.n 

2適用於折半查找的表的存儲方式及元素排列要求為(   )。

  A.鏈接方式存儲,元素無序            B.鏈接方式存儲,元素有序

C.順序方式存儲,元素無序            D.順序方式存儲,元素有序

3當在一個有序的順序表上查找一個數據時,既可用折半查找,也可用順序查找,但前者比后者的查找速度(   )。                      

A.必定快                            B.不一定    

C.在大部分情況下要快                D.取決於表遞增還是遞減

4折半查找有序表(4610122030507088100)。若查找表中元素58,則它將依次與表中(   )比較大小,查找結果是失敗。

A20703050                    B30887050   

C2050                            D308850

522個記錄的有序表作折半查找,當查找失敗時,至少需要比較(   )次關鍵字。

A3            B4          C5           D6

6折半搜索與二叉排序樹的時間性能(   )。

  A.相同                              B.完全不同        

C.有時不相同                        D.數量級都是O(log2n)

7分別以下列序列構造二叉排序樹,與用其它三個序列所構造的結果不同的是(   )。 

A.(10080, 90, 60, 120110130) 

B.(10012011013080, 60, 90

C10060, 80 90, 120110130

D(10080, 60, 90, 120130110)

8在平衡二叉樹中插入一個結點后造成了不平衡,設最低的不平衡結點為A,並已知A的左孩子的平衡因子為0右孩子的平衡因子為1,則應作(   )型調整以使其平衡。

ALL           BLR          CRL          DRR

9下列關於m階B-樹的說法錯誤的是(   )。    

A.根結點至多有m棵子樹     

B.所有葉子都在同一層次上

C非葉結點至少有m/2 (m為偶數)或m/2+1(m為奇數)棵子樹  

D根結點中的數據是有序的

10下面關於B-和B+樹的敘述中,不正確的是(   )。 

A.B-樹和B+樹都是平衡的多叉樹       B.B-樹和B+樹都可用於文件的索引結構

C.B-樹B+樹都能有效地支持順序檢索 D.B-樹和B+樹都能有效地支持隨機檢索

11m階B-樹是一棵(   )。

A.m叉排序樹                        B.m叉平衡排序樹  

C.m-1叉平衡排序樹                  D.m+1叉平衡排序樹

12下面關於哈希查找的說法,正確的是(   )。         

A.哈希函數構造的越復雜越好,因為這樣隨機性好,沖突小      

B.除留余數法是所有哈希函數中最好的   

C不存在特別好與壞的哈希函數,要視情況而定

D.哈希表的平均查找長度有時也和記錄總數有關

13下面關於哈希查找的說法,不正確的是(   )。              

  A.采用鏈地址法處理沖突時,查找一個元素的時間是相同的

      B.采用鏈地址法處理沖突時,若插入規定總是在鏈首,則插入任一個元素的時間是相同的

  C.用鏈地址法處理沖突,不會引起二次聚集現象

  D.用鏈地址法處理沖突,適合表長不確定的情況

14設哈希表長為14,哈希函數是H(key)=key%11,表中已有數據的關鍵字為15,38,61,84共四個,現要將關鍵字為49的元素加到表中,用二次探測法解決沖突,則放入的位置是(   )。

      A8              B3              C5             D

15)采用線性探測法處理沖突,可能要探測多個位置,在查找成功的情況下,所探測的這些位置上的關鍵字 (    )。

A不一定都是同義詞                  B一定都是同義詞       

C一定都不是同義詞                  D都相同

 

2.應用題

1假定對有序表:(34572430425463728795)進行折半查找,試回答下列問題:

① 畫出描述折半查找過程的判定樹;

② 若查找元素54,需依次與哪些元素比較?

③ 若查找元素90,需依次與哪些元素比較?

④ 假定每個元素的查找概率相等,求查找成功時的平均查找長度。

先畫出判定樹如下(注:mid=ë(1+12)/2û=6):

30

5           63

3     7       42        87

             4    24      54    72      95

查找元素54,需依次與30, 63, 42, 54 元素比較;

查找元素90,需依次與30, 63,87, 95元素比較;

ASL之前,需要統計每個元素的查找次數。判定樹的前3層共查找12×24×3=17次;

但最后一層未滿,不能用8×4,只能用5×4=20次,

所以ASL1/121720)=37/123.08

 

2在一棵空的二叉排序樹中依次插入關鍵字序列為1271711162139214請畫出所得到的二叉排序樹。

 

 

 

                 12

    17

       2     11        16    21              

        4   9        13

 

驗算方法:  用中序遍歷應得到排序結果:  2,4,7,9,11,12,13,16,1721

 

3已知如下所示長度為12的表:(Jan, Feb, Mar, Apr, May, June, July, Aug, Sep, Oct, Nov, Dec)

① 試按表中元素的順序依次插入一棵初始為空的二叉排序樹,畫出插入完成之后的二叉排序樹,並求其在等概率的情況下查找成功的平均查找長度。

② 若對表中元素先進行排序構成有序表,求在等概率的情況下對此有序表進行折半查找時查找成功的平均查找長度。

③ 按表中元素順序構造一棵平衡二叉排序樹,並求其在等概率的情況下查找成功的平均查找長度。

 

解:

 

 

 

 

    

 

 

    

4對下面的3階B-樹,依次執行下列操作,畫出各步操作的結果。

① 插入90    ②  插入25   ③  插入45   ④ 刪除60  

                       

 

 

 

 

 

 

 

 

 

5設哈希表的地址范圍為017,哈希函數為:Hkey=key%16。用線性探測法處理沖突,輸入關鍵字序列:(1024321731304647406349),構造哈希表,試回答下列問題:

① 畫出哈希表的示意圖;

② 若查找關鍵字63,需要依次與哪些關鍵字進行比較?

③ 若查找關鍵字60,需要依次與哪些關鍵字比較

④ 假定每個關鍵字的查找概率相等,求查找成功時的平均查找長度。

 

畫表如下:

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

32

17

63

49

 

 

 

 

24

40

10

 

 

 

30

31

46

47

查找63,首先要與H(63)=63%16=15號單元內容比較,即63 vs 31 ,no;

然后順移,與46,47,32,17,63相比,一共比較了6次!

③查找60,首先要與H(60)=60%16=12號單元內容比較,但因為12號單元為空(應當有空標記),所以應當只比較這一次即可。

對於黑色數據元素,各比較1次;共6次;

對紅色元素則各不相同,要統計移位的位數。63需要6次,“49”需要3次,“40”需要2次,“46”需要3次,“47”需要3次,

所以ASL=1/11623×3+6)=23/11

 

6設有一組關鍵字(9,01,23,14,55,20,84,27),采用哈希函數:H(key)=key %7 ,表長為10,用開放地址法的二次探測法處理沖突。要求:對該關鍵字序列構造哈希表,並計算查找成功的平均查找長度。

 

散列地址

0

1

2

3

4

5

6

7

8

9

關鍵字

14

01

9

23

84

27

55

20

 

 

比較次數

1

1

1

2

 3  

4

1

2

 

 

平均查找長度:ASLsucc=(1+1+1+2+3+4+1+2)/8=15/8

以關鍵字27為例:H(27)=27%7=6(沖突)   H1=(6+1)%10=7(沖突) 

H2=(6+22)%10=0(沖突)   H3=(6+33)%10=5   所以比較了4次。

 

(7設哈希函數H(K)=3 K mod 11,哈希地址空間為0~10,對關鍵字序列(32,13,49,24,38,21,4,12),按下述兩種解決沖突的方法構造哈希表,並分別求出等概率下查找成功時和查找失敗時的平均查找長度ASLsucc和ASLunsucc。

① 線性探測法;

② 鏈地址法。

 

散列地址

0

1

2

3

4

5

6

7

8

9

10

關鍵字

 

4

 

12

49

38

13

24

32

21

 

比較次數

 

1

 

1

1

2

1

2

1

2

 

ASLsucc =(1+1+1+2+1+2+1+2)/8=11/8

ASLunsucc=(1+2+1+8+7+6+5+4+3+2+1)/11=40/11

 

 ②

 

ASLsucc =(1*5+2*3)/8=11/8

ASLunsucc=(1+2+1+2+3+1+3+1+3+1+1)/11=19/11

5設哈希表的地址范圍為017,哈希函數為:Hkey=key%16。用線性探測法處理沖突,輸入關鍵字序列:(1024321731304647406349),構造哈希表,試回答下列問題:

① 畫出哈希表的示意圖;

② 若查找關鍵字63,需要依次與哪些關鍵字進行比較?

③ 若查找關鍵字60,需要依次與哪些關鍵字比較

④ 假定每個關鍵字的查找概率相等,求查找成功時的平均查找長度。

 

 

解: (1)畫表如下:

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

32

17

63

49

 

 

 

 

24

40

10

 

 

 

30

31

46

47

(2) 查找63,首先要與H(63)=63%16=15號單元內容比較,即63 vs 31 ,no;

然后順移,與46,47,32,17,63相比,一共比較了6次!

3)查找60,首先要與H(60)=60%16=12號單元內容比較,但因為12號單元為空(應當有空標記),所以應當只比較這一次即可。

4) 對於黑色數據元素,各比較1次;共6次;

對紅色元素則各不相同,要統計移位的位數。63需要6次,“49”需要3次,“40”需要2次,“46”需要3次,“47”需要3次,

所以ASL=1/11623×3+6)=23/11

 

6設有一組關鍵字(9,01,23,14,55,20,84,27),采用哈希函數:H(key)=key %7 ,表長為10,用開放地址法的二次探測法處理沖突。要求:對該關鍵字序列構造哈希表,並計算查找成功的平均查找長度。

 

散列地址

0

1

2

3

4

5

6

7

8

9

關鍵字

14

01

9

23

84

27

55

20

 

 

比較次數

1

1

1

2

 3  

4

1

2

 

 

平均查找長度:ASLsucc=(1+1+1+2+3+4+1+2)/8=15/8

以關鍵字27為例:H(27)=27%7=6(沖突)   H1=(6+1)%10=7(沖突) 

H2=(6+22)%10=0(沖突)   H3=(6+33)%10=5   所以比較了4次。

7設哈希函數H(K)=3 K mod 11,哈希地址空間為0~10,對關鍵字序列(32,13,49,24,38,21,4,12),按下述兩種解決沖突的方法構造哈希表,並分別求出等概率下查找成功時和查找失敗時的平均查找長度ASLsucc和ASLunsucc。

① 線性探測法;

② 鏈地址法。

 

散列地址

0

1

2

3

4

5

6

7

8

9

10

關鍵字

 

4

 

12

49

38

13

24

32

21

 

比較次數

 

1

 

1

1

2

1

2

1

2

 

ASLsucc =(1+1+1+2+1+2+1+2)/8=11/8

ASLunsucc=(1+2+1+8+7+6+5+4+3+2+1)/11=40/11

 


8章  排序

 

1.選擇題

1從未排序序列中依次取出元素與已排序序列中的元素進行比較,將其放入已排序序列的正確位置上的方法,這種排序方法稱為(   )。

A.歸並排序       B.冒泡排序        C.插入排序        D.選擇排序 

2從未排序序列中挑選元素,並將其依次放入已排序序列(初始時為空)的一端的方法,稱為(   )。

A.歸並排序       B.冒泡排序        C.插入排序        D.選擇排序 

3n個不同的關鍵字由小到大進行冒泡排序,在下列(   )情況下比較的次數最多。

A.從小到大排列好的                 B.從大到小排列好的    

 C.元素無序                         D.元素基本有序

4n個不同的排序碼進行冒泡排序,在元素無序的情況下比較的次數最多為(   )。

An+1            B.n               C.n-1              Dn(n-1)/2

5快速排序在下列(   )情況下最易發揮其長處。

A.被排序的數據中含有多個相同排序碼   

B.被排序的數據已基本有序   

C.被排序的數據完全無序         

D.被排序的數據中的最大值和最小值相差懸殊

6n個關鍵字作快速排序,在最壞情況下,算法的時間復雜度是(   )。

AO(n)           B.O(n2)            C.O(nlog2n)         D.O(n3) 

7若一組記錄的排序碼為(46, 7956384084),則利用快速排序的方法,以第一個記錄為基准得到的一次划分結果為(   )。

A384046567984               B.403846795684

C.403846567984               D.403846845679

8下列關鍵字序列中,(   )是堆。

A167231239453               B.942331721653 

C.16532394,3172               D162353319472

9堆是一種(   )排序。

A.插入         B.選擇         C.交換      D.歸並

10堆的形狀是一棵(   )。

A.二叉排序樹          B.滿二叉樹         C.完全二叉樹     D.平衡二叉樹

11若一組記錄的排序碼為(467956384084),則利用堆排序的方法建立的初始堆為(   )。

A794656384084               B.847956384046          

C.847956464038               D.845679404638

12下述幾種排序方法中,要求內存最大的是(   )。

A.希爾排序        B.快速排序         C.歸並排序       D.堆排序

13下述幾種排序方法中,(   )是穩定的排序方法。

A.希爾排序        B.快速排序         C.歸並排序       D.堆排序

14數據表中有10000個元素,如果僅要求求出其中最大的10個元素,則采用(    )算法最節省時間。

A.冒泡排序        B.快速排序         C簡單選擇排序   D.堆排序

15下列排序算法中,(   )不能保證每趟排序至少能將一個元素放到其最終的位置上。

A.希爾排序        B.快速排序         C.冒泡排序       D.堆排序

2.應用

1設待排序的關鍵字序列為{1221630281016*20618},試分別寫出使用以下排序方法,每趟排序結束后關鍵字序列的狀態。

① 直接插入排序

② 折半插入排序

③ 希爾排序(增量選取531

④ 冒泡排序

⑤ 快速排序

⑥ 簡單選擇排序

⑦ 堆排序

⑧ 二路歸並排序

 

①直接插入排序

[2    12]   16   30   28   10   16*   20   6    18         

[2    12    16]  30   28   10   16*   20   6    18         

[2    12    16   30]  28   10   16*   20   6    18         

[2    12    16   28   30]  10   16*   20   6    18         

[2    10    12   16   28  30]   16*   20   6    18         

[2    10    12   16   16*  28   30]   20   6    18         

[2    10    12   16   16*  20   28   30]   6    18         

[2    6     10   12   16  16*   20   28   30]   18         

[2    6     10   12   16  16*    18   20   28   30]

 

② 折半插入排序 排序過程同①

 

③ 希爾排序(增量選取531

10   2    16   6    18   12   16*   20  30    28 (增量選取5

6    2    12   10   18   16   16*   20  30    28 (增量選取3

2    6    10   12   16   16*  18      20  28    30 (增量選取1

 

④ 冒泡排序

2    12   16    28   10   16*  20   6     18   [30]        

2    12   16    10   16*  20   6    18    [28   30]        

2    12   10    16   16*  6     18   [20   28   30]          

2    10   12    16   6   16*    [18   20   28   30]          

2    10   12    6   16   [16*    18   20   28   30]         

2    10   6    12   [16   16*    18   20   28   30]        

2    6   10    [12   16   16*    18   20   28   30]

2    6   10    12   16   16*    18   20   28   30]       

 

⑤ 快速排序

12  [6    2  10]  12  [28  30  16*  20   16  18]          

6   [2]    [10]  12  [28  30  16*  20   16  18 ]         

28  2    6   10   12  [18  16  16*  20 ] 28  [30 ]       

18  2   6   10  12   [16*  16]  18  [20]  28  30          

16*     2   6   10  12   16* [16]   18  20   28  30

左子序列遞歸深度為1,右子序列遞歸深度為3

 

⑥ 簡單選擇排序

2    [12   16   30   28   10   16*   20   6    18]          

2    6    [16   30   28   10   16*   20   12   18]          

2    6    10   [30   28   16   16*   20   12   18]          

2    6    10   12   [28   16   16*   20   30   18]          

2    6    10   12   16   [28   16*   20   30   18]          

2    6    10   12   16   16*    [28  20   30   18]          

2    6    10   12   16   16*   18   [20   30   28]         

2    6    10   12   16   16*   18    20   [28  30]         

2    6    10   12   16   16*    18   20   28   [30]

 

 

⑧ 二路歸並排序

2 12    16 30    10 28    16 * 20    6 18                                                

2 12 16 30        10 16* 20 28       6 18                                               

2 10 12 16 16* 20 28 30   6 18                                               

2 6 10 12 16 16* 18 20 28 30

 

⑦ 堆排序

第一步,形成初始大根堆(詳細過程略),第二步做堆排序。

初始排序 不是大根堆                                      形成初始大根堆

交換110對象                                      從19重新形成堆

交換19對象                                      18重新形成堆

交換18對象                                      從17重新形成堆

交換17對象                                      從16重新形成堆

交換16對象                                      從15重新形成堆

交換15對象                                      從14重新形成堆

交換14對象                                      從13重新形成堆

交換13對象                                         從12重新形成堆

 

交換12對象                                      得到結果

 

2給出如下關鍵字序列{321,156,57,46,28,7,331,33,34,63},試按鏈式基數排序方法,列出每一趟分配和收集的過程。

3.算法設計

(1)試以單鏈表為存儲結構,實現簡單選擇排序算法。

void LinkedListSelectSort(LinkedList head)

//本算法一趟找出一個關鍵字最小的結點,其數據和當前結點進行交換;若要交換指針,則須記下

//當前結點和最小結點的前驅指針

p=head->next; 

while(p!=null)

    {q=p->next;  r=p;    //設r是指向關鍵字最小的結點的指針

     while (q!=null)

      {if(q->data<r->data) r=q;

 q:=q->next;

}

     if(r!=p)  r->data<-->p->data;

     p=p->next;

    }

2)有n個記錄存儲在帶頭結點的雙向鏈表中,現用雙向冒泡排序法對其按上升序進行排序,請寫出這種排序的算法。(注:雙向冒泡排序即相鄰兩趟排序向相反方向冒泡)。   

typedef struct node

       { ElemType data;

         struct node *prior,*next;

       }node*DLinkedList;

void  TwoWayBubbleSort(DLinkedList la)

//存儲在帶頭結點的雙向鏈表la中的元素進行雙向起泡排序。

{int exchange=1;  // 設標記 

 DLinkedList p,temp,tail;

 head=la         //雙向鏈表頭,算法過程中是向下起泡的開始結點

 tail=null;      //雙向鏈表尾,算法過程中是向上起泡的開始結點

while (exchange)

{p=head->next;    //p是工作指針,指向當前結點

exchange=0;      //假定本趟無交換 

while (p->next!=tail)  // 向下(右)起泡,一趟有一最大元素沉底

  if (p->data>p->next->data) //交換兩結點指針,涉及6條鏈

{temp=p->next; exchange=1;//有交換

p->next=temp->next;temp->next->prior=p //先將結點從鏈表上摘下

temp->next=p; p->prior->next=temp;     //temp插到p結點前

temp->prior=p->prior; p->prior=temp;

}

          else p=p->next; //無交換,指針后移

          tail=p; //准備向上起泡

          p=tail->prior;

while (exchange && p->prior!=head)  //向上(左)起泡,一趟有一最小元素冒出

     if (p->data<p->prior->data)       //交換兩結點指針,涉及6條鏈

{temp=p->prior; exchange=1;     //有交換

p->prior=temp->prior;temp->prior->next=p //先將temp結點從鏈表上摘下

temp->prior=p; p->next->prior=temp;        //temp插到p結點后(右)

temp->next=p->next; p->next=temp;

}

            else p=p->prior;  //無交換,指針前移

          head=p;             //准備向下起泡

 }// while (exchange)

} //算法結束 

3)設有順序放置的n個桶,每個桶中裝有一粒礫石,每粒礫石的顏色是紅,白,藍之一。要求重新安排這些礫石,使得所有紅色礫石在前,所有白色礫石居中,所有藍色礫石居后,重新安排時對每粒礫石的顏色只能看一次,並且只允許交換操作來調整礫石的位置。

[題目分析]利用快速排序思想解決。由於要求“對每粒礫石的顏色只能看一次”,設3個指針ijk,分別指向紅色、白色礫石的后一位置和待處理的當前元素。從k=n開始,從右向左搜索,若該元素是蘭色,則元素不動,指針左移(即k-1);若當前元素是紅色礫石,分i>=j(這時尚沒有白色礫石)和i<j兩種情況。前一情況執行第i個元素和第k個元素交換,之后i+1;后一情況,i所指的元素已處理過(白色),j所指的元素尚未處理,應先將ij所指元素交換,再將ik所指元素交換。對當前元素是白色礫石的情況,也可類似處理。

為方便處理,將三種礫石的顏色用整數123表示。

void QkSort(rectype r[],int n)

// r為含有n個元素的線性表,元素是具有紅、白和蘭色的礫石,用順序存儲結構存儲,

//本算法對其排序,使所有紅色礫石在前,白色居中,蘭色在最后。

{int i=1,j=1,k=n,temp;

 while (k!=j)

  {while (r[k].key==3) k--;// 當前元素是蘭色礫石,指針左移 

   if (r[k].key==1)        // 當前元素是紅色礫石

      if (i>=j){temp=r[k];r[k]=r[i];r[i]=temp; i++;}

//左側只有紅色礫石,交換r[k]r[i]

      else     {temp=r[j];r[j]=r[i];r[i]=temp; j++;  

  //左側已有紅色和白色礫石,先交換白色礫石到位 

temp=r[k];r[k]=r[i];r[i]=temp; i++;

//白色礫石(i所指)和待定礫石(j所指)

}   //再交換r[k]r[i],使紅色礫石入位。

if (r[k].key==2)

      if (i<=j) { temp=r[k];r[k]=r[j];r[j]=temp; j++;}

// 左側已有白色礫石,交換r[k]r[j] 

      else      { temp=r[k];r[k]=r[i];r[i]=temp; j=i+1;} 

//ij分別指向紅、白色礫石的后一位置

  }//while

 if (r[k]==2) j++;   /* 處理最后一粒礫石

 else if (r[k]==1) { temp=r[j];r[j]=r[i];r[i]=temp; i++; j++; }

 //最后紅、白、蘭色礫石的個數分別為: i-1;j-i;n-j+1 

}//結束QkSor算法

[算法討論]若將j(上面指向白色)看作工作指針,將r[1..j-1]作為紅色,r[j..k-1]為白色,r[k..n]為蘭色。從j=1開始查看,若r[j]為白色,則j=j+1;若r[j]為紅色,則交換r[j]r[i],且j=j+1i=i+1;若r[j]為蘭色,則交換r[j]r[k];k=k-1。算法進行到j>k為止。

算法片段如下:

int i=1,j=1,k=n;

while(j<=k)

  if (r[j]==1)  //當前元素是紅色

     {temp=r[i]; r[i]=r[j]; r[j]=temp; i++;j++; }

  else if (r[j]==2) j++;  //當前元素是白色

else               //(r[j]==3  當前元素是蘭色

         {temp=r[j]; r[j]=r[k]; r[k]=temp; k--; }

對比兩種算法,可以看出,正確選擇變量(指針)的重要性。

4)編寫算法,對n個關鍵字取整數值的記錄序列進行整理,以使所有關鍵字為負值的記錄排在關鍵字為非負值的記錄之前,要求:

① 采用順序存儲結構,至多使用一個記錄的輔助存儲空間;

② 算法的時間復雜度為O(n)

 

5)借助於快速排序的算法思想,在一組無序的記錄中查找給定關鍵字值等於key的記錄。設此組記錄存放於數組r[l..n]中。若查找成功,則輸出該記錄在r數組中的位置及其值,否則顯示“not find”信息。請簡要說明算法思想並編寫算法。

[題目分析]把待查記錄看作樞軸,先由后向前依次比較,若小於樞軸,則從前向后,直到查找成功返回其位置或失敗返回0為止。

     int index (RecType R[],int l,h,datatype key)

     {  int i=l,j=h;

        while (i<j)

        { while (i<=j && R[j].key>key) j--;

          if (R[j].key==key) return  j;

          while (i<=j && R[i].key<key) i++;

          if (R[i].key==key) return  i;

         }

        printf(“Not find”) ; return  0;

     }//index

 

6)有一種簡單的排序算法,叫做計數排序。這種排序算法對一個待排序的表進行排序,並將排序結果存放到另一個新的表中。必須注意的是,表中所有待排序的關鍵字互不相同,計數排序算法針對表中的每個記錄,掃描待排序的表一趟,統計表中有多少個記錄的關鍵字比該記錄的關鍵字小。假設針對某一個記錄,統計出的計數值為c,那么,這個記錄在新的有序表中的合適的存放位置即為c。

 ① 給出適用於計數排序的順序表定義;

 ② 編寫實現計數排序的算法;

 ③ 對於有n個記錄的表,關鍵字比較次數是多少?

 ④ 與簡單選擇排序相比較,這種方法是否更好?為什么?

typedef struct

int key; datatype info}RecType

void CountSort(RecType a[],b[],int n) //計數排序算法,將a中記錄排序放入b

for(i=0;i<n;i++) //對每一個元素

  { for(j=0,cnt=0;j<n;j++)

 if(a[j].key<a[i].key) cnt++; //統計關鍵字比它小的元素個數

    b[cnt]=a[i];

  }

}//Count_Sort

(3) 對於有n個記錄的表,關鍵碼比較n2次。

(4) 簡單選擇排序算法比本算法好。簡單選擇排序比較次數是n(n-1)/2,且只用一個交換記錄的空間;而這種方法比較次數是n2,且需要另一數組空間。

[算法討論]因題目要求“針對表中的每個記錄,掃描待排序的表一趟”,所以比較次數是n2次。若限制“對任意兩個記錄之間應該只進行一次比較”,則可把以上算法中的比較語句改為:

for(i=0;i<n;i++) a[i].count=0;//各元素再增加一個計數域,初始化為0

for(i=0;i<n;i++)

 for(j=i+1;j<n;j++) 

 if(a[i].key<a[j].key) a[j].count++; else a[i].count++;

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM