數據結構與算法考試習題
- 第一章 求算法複雜度與頻率
- 第二章 線性表
- 2.2 填空題。
- 2.3何時選用順序表、何時選用鏈表作爲線性表的存儲結構爲宜?
- 2.7 已知L是帶表頭結點的非空單鏈表,且P結點既不是首元結點,也不是尾元結點,試從下列提供的答案中選擇合適的語句序列。
- 2.8 已知P結點是某雙向鏈表的中間結點,試從下列提供的答案中選擇合適的語句序列。
- 2.11 設順序表va中的數據元素遞增有序。試寫一算法,將x插入到順序表的適當位置上,以保持該表的有序性。
- 2.21 試寫一算法,實現順序表的就地逆置,即利用原表的存儲空間將線性表 逆置爲 。
- 2.24 假設有兩個按元素值遞增有序排列的線性表A和B,均以單鏈表作存儲結構,請編寫算法將A表和B表歸併成一個按元素值遞減有序(即非遞增有序,允許表中含有值相同的元素)排列的線性表C,並要求利用原表(即A表和B表)的結點空間構造C表。
- 2.31 假設某個單向循環鏈表的長度大於1,且表中既無頭結點也無頭指針。已知s爲指向鏈表中某個結點的指針,試編寫算法在鏈表中刪除指針s所指結點的前驅結點。
- 2.32 已知有一個單向循環鏈表,其每個結點中含三個域:pre,data和next,其中data爲數據域,next爲指向後繼結點的指針域,prior也爲指針域,但它的值爲空(NULL),試編寫算法將此單向循環鏈表改爲雙向循環鏈表,即使prior成爲指向前驅結點的指針域。
- 第3章 棧和隊列
- 3.2 簡述棧和線性表的差別。
- 3.3 寫出下列程序段的輸出結果(棧的元素類型SElemType爲char)。
- 3.12 寫出以下程序段的輸出結果(隊列中的元素類型QElemType爲char)。
- 3.28 假設以帶頭結點的循環鏈表表示隊列,並且只設一個指針指向隊尾元素結點(注意不設頭指針),試編寫相應的隊列初始化、入隊列和出隊列的算法。
- 3.13 假設以帶頭結點的循環鏈表表示隊列,並且只設一個指針指向隊尾元素站點(注意不設頭指針) ,試編寫相應的置空隊、判隊空 、入隊和出隊等算法。
- 3.30 假設將循環隊列定義爲:以域變量rear和length分別指示循環隊列中隊尾元素的位置和內含元素的個數。試給出此循環隊列的隊滿條件,並寫出相應的入隊列和出隊列的算法(在出隊列的算法中要返回隊頭元素)。
- 3.15 假設循環隊列中只設rear和quelen 來分別指示隊尾元素的位置和隊中元素的個數,試給出判別此循環隊列的隊滿條件,並寫出相應的入隊和出隊算法,要求出隊時需返回隊頭元素。
- 第六章樹和二叉樹
第一章 求算法複雜度與頻率
設n爲正整數,試確定下列各程序中前置以記號@的語句的頻度和算法複雜度。
(1) i=1; k=0;
while( i <= n-1)
{ k+=10*i; @ n-1
i++;
}
分析:T(n)=O(n)
(2) i=1; k=0;
do
{ k+=10*i; @ n-1
i++;
}while((i<=n-1);
分析:T(n)=O(n)
(3) i=1; k=0;
while(i<=n-1)
{ i++;
k+=10*i; @ n-1
}
分析:T(n)=O(n)
(4) k=0;
for(i=1; i<=n; i++){
for(j=i; j<=n; j++)
k++; @ n(n+1)/2
}
分析:
(5) for(i=1; i<=n; i++)
{
for(j=1;j<=i; j++)
{
for(k=1; k<=j; k++)
X += delta; @ n(n+1)(n+2)/6
}
}
分析:
(6) i=1; j=0;
while( i+j <= n)
{
if(i>j) @ n
j++;
else
i++;
}
分析:
通過分析以上程序段,可將i+j看成一個控制循環次數的變量,且每執行一次循環,i+j的值加1。該程序段的主要時間消耗是while循環,而while循環共做了n次,所以該程序段的執行時間爲:T(n)=O(n)
第二章 線性表
2.2 填空題。
(1) 在順序表中插入或刪除一個元素,需要平均移動 元素,具體移動的元素個數與 有關。
(2) 順序表中邏輯上相鄰的元素的物理位置 緊鄰。單鏈表中邏輯上相鄰的元素的物理位置 緊鄰。
(3) 在單鏈表中,除了首元結點外,任一結點的存儲位置由 指示。
(4) 在單鏈表中設置頭結點的作用是 。
1、在順序表中插入或刪除一個元素,需要平均移動(表中一半)元素,具體移動的元素個數與(表長和該元素在表中的位置)有關。
2、順序表中邏輯上相鄰的元素的物理位置(必定)相鄰。單鏈表中邏輯上相鄰的元素的物理位置(不一定)相鄰。
3、在單鏈表中,除了首元結點外,任一結點的存儲位置由(其直接前驅結點的鏈域的值)指示。
4、在單鏈表中設置頭結點的作用是(插入和刪除元素不必進行特殊處理)。
2.3何時選用順序表、何時選用鏈表作爲線性表的存儲結構爲宜?
答:在實際應用中,應根據具體問題的要求和性質來選擇順序表或鏈表作爲線性表的存儲結構,通常有以下幾方面的考慮:
1.基於空間的考慮。當要求存儲的線性表長度變化不大,易於事先確定其大小時,爲了節約存儲空間,宜採用順序表;反之,當線性表長度變化大,難以估計其存儲規模時,採用動態鏈表作爲存儲結構爲好。
2.基於時間的考慮。若線性表的操作主要是進行查找,很少做插入和刪除操作時,採用順序表做存儲結構爲宜;反之, 若需要對線性表進行頻繁地插入或刪除等的操作時,宜採用鏈表做存儲結構。並且,若鏈表的插入和刪除主要發生在表的首尾兩端,則採用尾指針表示的單循環鏈表爲宜。
2.7 已知L是帶表頭結點的非空單鏈表,且P結點既不是首元結點,也不是尾元結點,試從下列提供的答案中選擇合適的語句序列。
a. 刪除P結點的直接後繼結點的語句序列是____________________。
b. 刪除P結點的直接前驅結點的語句序列是____________________。
c. 刪除P結點的語句序列是____________________。
d. 刪除首元結點的語句序列是____________________。
e. 刪除尾元結點的語句序列是____________________。
(1) P=P->next;
(2) P->next=P;
(3) P->next=P->next->next;
(4) P=P->next->next;
(5) while(P!=NULL) P=P->next;
(6) while(Q->next!=NULL) { P=Q; Q=Q->next; }
(7) while(P->next!=Q) P=P->next;
(8) while(P->next->next!=Q) P=P->next;
(9) while(P->next->next!=NULL) P=P->next;
(10) Q=P;
(11) Q=P->next;
(12) P=L;
(13) L=L->next;
(14) free(Q);
解:a. (11) (3) (14)
b. (10) (12) (8) (3) (14)
c. (10) (12) (7) (3) (14)
d. (12) (11) (3) (14)
e. (9) (11) (3) (14)
2.8 已知P結點是某雙向鏈表的中間結點,試從下列提供的答案中選擇合適的語句序列。
a. 在P結點後插入S結點的語句序列是_______________________。
b. 在P結點前插入S結點的語句序列是_______________________。
c. 刪除P結點的直接後繼結點的語句序列是_______________________。
d. 刪除P結點的直接前驅結點的語句序列是_______________________。
e. 刪除P結點的語句序列是_______________________。
(1) P->next=P->next->next;
(2) P->priou=P->priou->priou;
(3) P->next=S;
(4) P->priou=S;
(5) S->next=P;
(6) S->priou=P;
(7) S->next=P->next;
(8) S->priou=P->priou;
(9) P->priou->next=P->next;
(10) P->priou->next=P;
(11) P->next->priou=P;
(12) P->next->priou=S;
(13) P->priou->next=S;
(14) P->next->priou=P->priou;
(15) Q=P->next;
(16) Q=P->priou;
(17) free§;
(18) free(Q);
解:a. (7) (6) (12)(3) (12和3的位置不能反,反了就錯了)
b. (8) (5) (13)(4)
c. (15) (1) (11) (18)
d. (16) (2) (10) (18)
e. (14) (9) (17)
2.11 設順序表va中的數據元素遞增有序。試寫一算法,將x插入到順序表的適當位置上,以保持該表的有序性。
解:
Status InsertOrderList(SqList &va,ElemType x)
{
//在非遞減的順序表va中插入元素x並使其仍成爲順序表的算法
int i;
if(va.length==va.listsize)return(OVERFLOW);
for(i=va.length;i>0,x<va.elem[i-1];i--)
va.elem[i]=va.elem[i-1];
va.elem[i]=x;
va.length++;
return OK;
}
其他寫法:
答:因已知順序表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-1 ] ; // 比較並移動元素
L->data[ i ] =x;
L -> length++;
}
2.21 試寫一算法,實現順序表的就地逆置,即利用原表的存儲空間將線性表 逆置爲 。
解:
// 順序表的逆置
Status ListOppose_Sq(SqList &L)
{
int i;
ElemType x;
for(i=0;i<L.length/2;i++){
x=L.elem[i];
L.elem[i]=L.elem[L.length-1-i];
L.elem[L.length-1-i]=x;
}
return OK;
}
其他寫法:
答:
要將該表逆置,可以將表中的開始結點與終端結點互換,第二個結點與倒數第二個結點互換,如此反覆,就可將整個表逆置了。算法如下:
// 順序表結構定義同上題
void ReverseList( Seqlist *L)
{
DataType temp ; //設置臨時空間用於存放data
int i;
for (i=0;i<L->length/2;i++)//L->length/2爲整除運算
{ temp = L->data[i]; //交換數據
L -> data[ i ] = L -> data[ L -> length-1-i];
L -> data[ L -> length - 1 - i ] = temp;
}
}
2.24 假設有兩個按元素值遞增有序排列的線性表A和B,均以單鏈表作存儲結構,請編寫算法將A表和B表歸併成一個按元素值遞減有序(即非遞增有序,允許表中含有值相同的元素)排列的線性表C,並要求利用原表(即A表和B表)的結點空間構造C表。
解:
// 將合併逆置後的結果放在C表中,並刪除B表
Status ListMergeOppose_L(LinkList &A,LinkList &B,LinkList &C)
{
LinkList pa,pb,qa,qb;
pa=A;
pb=B;
qa=pa; // 保存pa的前驅指針
qb=pb; // 保存pb的前驅指針
pa=pa->next;
pb=pb->next;
A->next=NULL;
C=A;
while(pa&&pb){
if(pa->data<pb->data){
qa=pa;
pa=pa->next;
qa->next=A->next; //將當前最小結點插入A表表頭
A->next=qa;
}
else{
qb=pb;
pb=pb->next;
qb->next=A->next; //將當前最小結點插入A表表頭
A->next=qb;
}
}
while(pa){
qa=pa;
pa=pa->next;
qa->next=A->next;
A->next=qa;
}
while(pb){
qb=pb;
pb=pb->next;
qb->next=A->next;
A->next=qb;
}
pb=B;
free(pb);
return OK;
}
其他寫法:
解:
void reverse_merge(LinkList &A,LinkList &B,LinkList &C)//把元素遞增排列的鏈表A和B合併爲C,且C中元素遞減排列,使用原空間
{
pa=A->next;pb=B->next;pre=NULL; //pa和pb分別指向A,B的當前元素
while(pa||pb)
{
if(pa->data<pb->data||!pb)
{
pc=pa;q=pa->next;pa->next=pre;pa=q; //將A的元素插入新表
}
else
{
pc=pb;q=pb->next;pb->next=pre;pb=q; //將B的元素插入新表
}
pre=pc;
}
C=A;A->next=pc; //構造新表頭
}//reverse_merge
分析:本算法的思想是,按從小到大的順序依次把A和B的元素插入新表的頭部pc處,最後處理A或B的剩餘元素.
2.31 假設某個單向循環鏈表的長度大於1,且表中既無頭結點也無頭指針。已知s爲指向鏈表中某個結點的指針,試編寫算法在鏈表中刪除指針s所指結點的前驅結點。
解:
// 在單循環鏈表S中刪除S的前驅結點
Status ListDelete_CL(LinkList &S)
{
LinkList p,q;
if(S==S->next)return ERROR;
q=S;
p=S->next;
while(p->next!=S){
q=p;
p=p->next;
}
q->next=p->next;
free(p);
return OK;
}
其他寫法:
解:
已知指向這個結點的指針是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.32 已知有一個單向循環鏈表,其每個結點中含三個域:pre,data和next,其中data爲數據域,next爲指向後繼結點的指針域,prior也爲指針域,但它的值爲空(NULL),試編寫算法將此單向循環鏈表改爲雙向循環鏈表,即使prior成爲指向前驅結點的指針域。
解:
// 建立一個空的循環鏈表
Status InitList_DL(DuLinkList &L)
{
L=(DuLinkList)malloc(sizeof(DuLNode));
if(!L) exit(OVERFLOW);
L->pre=NULL;
L->next=L;
return OK;
}
// 向循環鏈表中插入一個結點
Status ListInsert_DL(DuLinkList &L,ElemType e)
{
DuLinkList p;
p=(DuLinkList)malloc(sizeof(DuLNode));
if(!p) return ERROR;
p->data=e;
p->next=L->next;
L->next=p;
return OK;
}
// 將單循環鏈表改成雙向鏈表
Status ListCirToDu(DuLinkList &L)
{
DuLinkList p,q;
q=L;
p=L->next;
while(p!=L){
p->pre=q;
q=p;
p=p->next;
}
if(p==L) p->pre=q;
return OK;
}
其他寫法:
Status DuLNode_Pre(DuLinkList &L)//完成雙向循環鏈表結點的pre域
{
for(p=L;!p->next->pre;p=p->next) p->next->pre=p;
return OK;
}//DuLNode_Pre
第3章 棧和隊列
3.2 簡述棧和線性表的差別。
解:線性表是具有相同特性的數據元素的一個有限序列。棧是限定僅在表尾進行插入或刪除操作的線性表。
3.3 寫出下列程序段的輸出結果(棧的元素類型SElemType爲char)。
void main()
{
Stack S;
char x,y;
InitStack(S);
x= ‘c’; y= ‘k’;
Push(S,x); Push(S, ‘a’); Push(S,y);
Pop(S,x); Push(S, ‘t’); Push(S,x);
Pop(S,x); Push(S, ‘s’);
while(!StackEmpty(S)) { Pop(S,y); printf(y); }
printf(x);
}
解:stack
void main( ){
Stack S;
Char x,y;
InitStack(S);
x=’c’;y=’k’; //x=‘c’, y=‘k’
Push(S,x); //x=‘c’,y=‘k’,S=“c”
Push(S,’a’); //x=‘c’,y=‘k’,S=“ac”
Push(S,y); //x=‘c’, y=‘k’, S=“kac”
Pop(S,x); //x=‘k’,y=‘k’,S=“ac”
Push(S,’t’); //x=‘k’,y=‘k’,S=“tac”
Push(S,x); //x=‘k’,y=‘k’,S=“ktac”
Pop(S,x); //x=‘k’,y=‘k’ S=“tac”
Push(S,’s’); //x=‘k’,y=‘k’ S=“stac”
while(!StackEmpty(S))
{
Pop(S,y); //依次爲y=‘s’,y=‘t’,y=‘a’,y=‘c’
printf(y); //打印依次爲s,t,a,c
}
Printf(x);//x=‘k’
}
所以最後應該是stack
3.12 寫出以下程序段的輸出結果(隊列中的元素類型QElemType爲char)。
void main()
{
Queue Q;
InitQueue(Q);
char x= ‘e’, y= ‘c’;
EnQueue(Q, ‘h’);
EnQueue(Q, ‘r’);
EnQueue(Q, y);
DeQueue(Q, x);
EnQueue(Q, x);
DeQueue(Q, x);
EnQueue(Q, ‘a’);
While(!QueueEmpty(Q))
{
DeQueue(Q,y);
cout<<y;
}
cout<<x;
}
解:char
3.28 假設以帶頭結點的循環鏈表表示隊列,並且只設一個指針指向隊尾元素結點(注意不設頭指針),試編寫相應的隊列初始化、入隊列和出隊列的算法。
解:
typedef int ElemType;
typedef struct NodeType{
ElemType data;
NodeType *next;
}QNode,*QPtr;
typedef struct{
QPtr rear;
int size;
}Queue;
Status InitQueue(Queue& q)
{
q.rear=NULL;
q.size=0;
return OK;
}
Status EnQueue(Queue& q,ElemType e)
{
QPtr p;
p=new QNode;
if(!p) return FALSE;
p->data=e;
if(!q.rear){
q.rear=p;
p->next=q.rear;
}
else{
p->next=q.rear->next;
q.rear->next=p;
q.rear=p;
}
q.size++;
return OK;
}
Status DeQueue(Queue& q,ElemType& e)
{
QPtr p;
if(q.size==0)return FALSE;
if(q.size==1){
p=q.rear;
e=p->data;
q.rear=NULL;
delete p;
}
else{
p=q.rear->next;
e=p->data;
q.rear->next=p->next;
delete p;
}
q.size--;
return OK;
}
其他解法:
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.30 假設將循環隊列定義爲:以域變量rear和length分別指示循環隊列中隊尾元素的位置和內含元素的個數。試給出此循環隊列的隊滿條件,並寫出相應的入隊列和出隊列的算法(在出隊列的算法中要返回隊頭元素)。
解:
#define MaxQSize 4
typedef int ElemType;
typedef struct{
ElemType *base;
int rear;
int length;
}Queue;
Status InitQueue(Queue& q)
{
q.base=new ElemType[MaxQSize];
if(!q.base) return FALSE;
q.rear=0;
q.length=0;
return OK;
}
Status EnQueue(Queue& q,ElemType e)
{
if((q.rear+1)%MaxQSize==(q.rear+MaxQSize-q.length)%MaxQSize)
return FALSE;
else{
q.base[q.rear]=e;
q.rear=(q.rear+1)%MaxQSize;
q.length++;
}
return OK;
}
Status DeQueue(Queue& q,ElemType& e)
{
if((q.rear+MaxQSize-q.length)%MaxQSize==q.rear)
return FALSE;
else{
e=q.base[(q.rear+MaxQSize-q.length)%MaxQSize];
q.length--;
}
return OK;
}
其他寫法:
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];
}
第六章樹和二叉樹
6.3 試分別畫出具有3個結點的樹和3個結點的二叉樹的所有不同形態。
解:含三個結點的樹只有兩種:(1)和(2);而含3個結點的二叉樹可能有下列3種形態:(一),(二),(三),(四),(五)。
注意,(2)和(三)是完全不同的結構。
6.15 請對右圖所示二叉樹進行後序線索化,爲每個空指計建立相應的前驅或後繼線索
解:
6.27假設一棵二叉樹的先序序列爲EBADCFHGIKJ和中序序列爲ABCDEFGHIJK。請畫出該樹。
解:
6.28假設一棵二叉樹的中序序爲DCBGEAHFIJK和後序序列爲DCEGBFHKJIA.請畫出該樹。
解: