Tải bản đầy đủ (.pdf) (49 trang)

Đề thi Kỹ thuật lập trình có lời giải

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (9.04 MB, 49 trang )

Đề thi số 1
Môn: Kỹ thuật lập trình (IT3040)
Thời gian: 90 phút
SV được phép sử dụng tài liệu, nhưng không được trao đổi tài liệu, máy tính, điện thoại dđ

Câu 1 (0.75đ): Tinh chỉnh đoạn mã sau và giải thích:
int letter_count(const char *buf, int size){

Câu 6 (1đ): Tối ưu đoạn code sau:
int i,m,n,p;

int count, i;

float c,*a,*b,*c,*d;

count = 0;

/* Gán giá trị các biến cần thiết */

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

for(int i=0; i<4*n; i++){
a[i]=b[i]+c[i]+d[i]+47-5*sin(c)+3*tan(c);

if ((buf[i] >= 'A' && buf[i] <= 'Z') ||
(buf[i] <= 'z' && buf[i] >= 'a'))
count++;

m=16*n + 512*p – 17*sin(c);
}


}
return count;
}

Câu 2 (1đ): Cho biết giá trị các phần tử của mảng B sau
khi thực hiện đoạn lệnh dưới đây:
int B[] = {2,4,7,8,9,15};
int c,*p=&B[1];
*p+=1; c=*p--;

Câu 7 (2đ): Cho một danh sách liên kết đơn với các nút
được khai báo như sau:
typedef struct node{
int data;
node *pNext;
}NODE;

Viết hàm để tìm phần tử lẻ nhỏ nhất trong danh sách.
Nếu không tìm thấy phần tử lẻ thì trả về 0.

p+=3; *p+=c;

Câu 3 (0.75đ): Phân tích đoạn mã lệnh sau, tìm các lỗi
sai (nếu có) rồi sửa lại:

Câu 8 (1đ): Đoạn mã sau thực hiện việc copy xâu từ
xâu nguồn src sang xâu đích dest. Phân tích, tìm và sửa
các lỗi sai (nếu có):

i=0;


void strcpy(char *dest, char *src)

do {

{

putchar(s[i++]) ;

int i;

putchar('\n');

for (i = 0; src[i] != '\O'; i++)
dest[i] = src[i];

) while (s[i] != '\0');

Câu 4 (2.5đ): Viết hàm đệ quy void chuyenCoSo(int
base, int number) để chuyển đổi một số nguyên
number từ hệ cơ số 10 sang hệ cơ số bất kì base (từ 2
đến 16). Sau đó viết lại hàm dưới dạng không đệ quy.
Câu 5 (1đ): Xây dựng cấu trúc phân số gồm tử số và
mẫu số là các số nguyên. Thực hiện đa năng hoá toán tử
~ có chức năng giản ước phân số.
Ví dụ: ~(10/6) = 5/3

}



ĐÁP ÁN ĐỀ THI SỐ 1
Câu 1 (0.75đ): Nhìn sơ qua đoạn code ta có thể thấy được đoạn code này đếm số kí tự là chữ cái. Tuy nhiên vấn đề ở chỗ các biểu
thức logic trong vòng lặp if có thứ tự chưa được tối ưu lắm.
Tổng quát như sau: giả sử ta có biểu thức logic dạng (E1 OR E2). Nếu E1 có giá trị TRUE thì cả biểu thức là TRUE và trình dịch sẽ
không xem xét giá trị logic của E2 đằng sau nữa. Do đó ta nên đặt biểu thức có xác suất TRUE lớn nhất lên đầu đối với biểu thức
dạng trên.
Tương tự với biểu thức logic chứa AND: (E1 AND E2). Nếu E1 có giá trị FALSE thì cả biểu thức là FALSE. Do đó ta đưa biểu thức
có xác suất FALSE lớn nhất lên đầu.
Từ những lập luận trên, ta có đoạn code tối ưu như sau:
int letter_count(const char *buf, int size)
{
int count, i;
count = 0;
for (i = 0; i < size; i++) {
if ((buf[i] >= 'a' && buf[i] <= 'z') ||
(buf[i] >= 'A' && buf[i] <= 'Z'))
count++;
}
return count;
}
Trong 1 đoạn văn bản thì xác suất gặp chữ thường là nhiều hơn cả, do đó đặt biểu thức kiểm tra chữ thường lên đầu. Chưa hết, trong
1 text file thì thường các ký tự có giá trị nhỏ hơn bằng 'z'. Và khi đó, rõ ràng biểu thức (buf[i] <= 'z' && buf[i] >= 'a') không tối ưu
bằng (buf[i] >= 'a' && buf[i] <= 'z').

Câu 2 (1đ): Bài này đơn giản, để gỡ điểm.
Ở đây ban đầu p trỏ tới B[1]. *p+=1 ~ B[1]+=1 => B[1] = 4+1=5.
c=*p-- ~ c=*p và p-- => c=B[1]=5, p trỏ tới B[0].
p+=3 => p trỏ tới B[3].
*p+=c => B[3]+=5 => B[3] = 13.
Vậy giá trị các phần tử mảng sau khi thực hiện đoạn lệnh là B[] = {2,5,7,13,9,15}.


Câu 3 (0.75đ): Đoạn code này thực hiện việc in từng phần tử của chuỗi thành từng dòng. Ở đây sử dụng vòng lặp do-while. Do đó
sẽ gặp lỗi nếu xâu s là xâu rỗng.
Sửa lại như sau bằng cách đổi sang vòng lặp for:
for (i = 0; s[i] != '\0'; i++){
putchar(s[i]);
putchar('\n');
}
Phát hiện lỗi: 0.5đ, sửa lỗi: 0.25đ.

Câu 4 (2.5đ): Hàm đệ quy 1.25đ, hàm không đệ quy 1.25đ.
//HÀM ĐỆ QUY


void chuyenCoSo(int base, int number)
{
char digits[]={'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
if(number<=0)
{
return;
}
//Gọi đệ quy hàm chuyenCoSo()
chuyenCoSo(base,number/base);
//Xuất kết quả ra màn hình
printf("%c",digits[number%base]);
}
//HÀM KHÔNG ĐỆ QUY
void chuyenCoSo_nonRec(int base, int number)
{
//Mảng chứa các chữ số của hệ 16 [hex]

char digits[]={'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
char output[100];
int i=0;
while(number>0)
{
output[i++] = digits[number%base];
number = number/base;
}
i--;
//Xuất ra kết quả
for(;i>=0;i--)
{
printf("%c",output[i]);
}
}

Câu 5 (1đ): Ở đây đề bài yêu cầu phải tối giản phân số. Do đó ta cần đưa vào hàm tìm ước chung lớn nhất của tử số và mẫu số.
Xét 2 hàm tìm UCLN (sử dụng đệ quy cho nhanh gọn) như dưới đây. (0.25đ)
//Cách 1:
int UCLN(int a, int b)
{
return b ? UCLN(b, a % b) : a;
}
//Cách 2:
int UCLN( int x, int y)
{
if(x==y)
return x;
else if(x>y)
return UCLN(x-y,y);

else
return UCLN(x,y-x);
}
Xây dựng cấu trúc phân số (0.25đ):
typedef struct PhanSo{


int TuSo, MauSo;
}PS;
Đa năng hoá toán tử ~ có chức năng tối giản phân số (0.5đ)
PS operator ~ (PS &a){
a.TuSo/=UCLN(a.TuSo, a.MauSo);
a.MauSo/=UCLN(a.TuSo, a.MauSo);
return a;
}

Câu 6 (1đ): Tối ưu đoạn code…
+ Đặt các biểu thức bất biến là hằng số (0.25đ):
float temp1=47-5*sin(c)+3*tan(c);
Có thể một số bạn sẽ đặt thêm:
float temp2=17*sin(c); .Tuy nhiên biểu thức m = … không phụ thuộc vòng lặp for nên ta không cần dùng biến temp2 mà chỉ
cần bỏ biểu thức m = … ra khỏi vòng lặp for là xong.
+ Thay đổi các phép nhân với 2k bằng các phép dịch bit để tăng tốc độ tính toán (0.25đ):
16*n+512*p = n<<4 + p<<9
+ Nhìn vào vòng lặp for ta thấy cận trên và bước nhảy là 4*n, i++, tức là phải thực hiện 4*n lần lặp. Có thể tối ưu bằng cách giảm bớt
số lần lặp xuống còn n lần như sau: (kĩ thuật này còn gọi là kĩ thuật Loop Unrolling, các bạn có thể tìm hiểu thêm trên Google) (0.5đ)
for(i=0; i<4*n; i+=4){
a[i]=b[i]+c[i]+d[i]+temp1;
a[i+1]=b[i+1]+c[i+1]+d[i+1]+temp1;
a[i+2]=b[i+2]+c[i+2]+d[i+2]+temp1;

a[i+3]=b[i+3]+c[i+3]+d[i+3]+temp1;
}
Tổng hợp lại ta có đoạn code tối ưu hơn như sau:
int i,m,n,p;
float c,*a,*b,*c,*d;
/* Gán giá trị các biến cần thiết */
float temp1=47-5*sin(c)+3*tan(c);
m=n<<4 + p<<9 – 17*sin(c);
for(i=0;i<4*n;i+=4){
a[i]=b[i]+c[i]+d[i]+temp1;
a[i+1]=b[i+1]+c[i+1]+d[i+1]+temp1;
a[i+2]=b[i+2]+c[i+2]+d[i+2]+temp1;
a[i+3]=b[i+3]+c[i+3]+d[i+3]+temp1;
}

Câu 7 (2đ): Đề yêu cầu đưa ra phần tử lẻ nhỏ nhất của danh sách. Sử dụng ý tưởng sau:
+ Duyệt toàn bộ danh sách và đếm số phần tử lẻ trong danh sách. Nếu không có phần tử lẻ thì trả về 0 ngay.
+ Nếu có phần tử lẻ thì lưu các phần tử lẻ này vào 1 mảng cấp phát động với kích thước = số phần tử lẻ (cho tiết kiệm bộ nhớ)
+ Duyệt mảng này và tìm phần tử nhỏ nhất. Cái này thì quá đơn giản và quen thuộc.
//Hàm đếm số phần tử lẻ trong danh sách
int demSoPtLe(node *head){
int k=0;


node *tmp;
for(tmp=head; tmp!=NULL; tmp=tmp->next){
if (tmp->data % 2 == 1)
k++;
}
return k;

}
//Hàm tìm phần tử lẻ nhỏ nhất
int minOdd(node *head){
if (demSoPtLe(head) == 0) return 0;
else{
node *tmp;
int m=demSoPtLe(head);
int *a=new int [m];
int i=0,min;
for(tmp=head;tmp!=NULL;tmp=tmp->next){
if((tmp->data)%2==1)
a[i++]=tmp->data; //nếu gặp phần tử lẻ thì lưu vào mảng a
}
min=a[0];
for(i=0; iif(a[i]min=a[i];
}
return min;
}
}

Câu 8 (1đ): Hàm strcpy là hàm mà chúng ta thường sử dụng dưới dạng ‘mì ăn liền’. Tuy nhiên việc hiểu bản chất của hàm này thì
chỉ có 1 số bạn làm được.
Chép chuỗi được trỏ bởi con trỏ src vào một mảng được trỏ bởi con trỏ dest, bao gồm cả kí tự NULL (kí tự kết thúc xâu, \0).
Để tránh tràn thì kích thước của mảng được trỏ bởi con trỏ dest phải đủ lớn để chứa xâu trỏ bởi con trỏ src bao gồm cả kí tự NULL
và không được chồng lên vùng nhớ được trỏ bởi src.
Do đó ta sửa lại như sau bằng cách chỉnh lại điều kiện trong vòng lặp. (có thể dùng kiểu void và không trả về giá trị dest như đoạn
code ban đầu cũng được):
char* strcpy(char * dest, const char *src);

{
int i,n;
n=strlen(src);
for(i=0; i<=n; ++i)
dest[i] = src[i];
return dest;
}


Đề thi số 2
Môn: Kỹ thuật lập trình (IT3040)
Thời gian: 90 phút
SV được phép sử dụng tài liệu, nhưng không được trao đổi tài liệu, máy tính, điện thoại dđ

Câu 1 (0.75đ): Tinh chỉnh đoạn mã sau và giải thích:
int calc(int subkey, int bitoff){
subkey = subkey >>
(bitoff - ((bitoff >> 5) << 5));
return subkey;
}

Câu 2 (1đ): Cho biết giá trị các phần tử của mảng A sau
khi thực hiện đoạn lệnh dưới đây:
int A[] = {5,7,19,1,2,6};
int b, *p=A+2;
b=*p++;

Câu 6 (0.75đ): Phân tích đoạn mã lệnh sau, tìm các lỗi
sai (nếu có) và sửa lại cho đúng:
char *p, buf[256];

gets(buf);
p = malloc(strlen(buf));
strcpy(p, buf);

Câu 7 (2đ): Cho một danh sách liên kết đơn với các nút
được khai báo như sau:
typedef struct node{
int data;

*p+=2*b;
p++;
*p+=1;

Câu 3 (2.5đ): Viết hàm đệ quy void chuyenCoSo (int
base, int number) để chuyển đổi một số nguyên
number từ hệ cơ số 10 sang hệ cơ số bất kì base (từ 2
đến 16). Sau đó viết lại hàm dưới dạng không đệ quy.

Câu 4 (1đ): Xây dựng cấu trúc số phức gồm phần thực
và phần ảo là các số thực. Thực hiện đa năng hoá toán
tử ~ có chức năng đưa ra số phức liên hợp của số phức
ban đầu.

node *pNext;
}NODE;

Viết hàm để tìm phần tử chẵn lớn nhất trong danh sách.
Nếu không tìm thấy phần tử chẵn thì trả về 0.
Câu 8 (1đ):
a. Đoạn mã sau thực hiện việc tính giai thừa của một số

nguyên n. Phân tích đoạn mã, tìm các lỗi sai và sửa
lại cho đúng (0.5đ):
void factorial(int n){
int fac=1;
while (n--)
fac *= n;
return fac;

Ví dụ: ~(4-3i) = 4+3i
Câu 5 (1đ): Tối ưu đoạn code sau:
int x,y,z,i,n,*p,*q,*s;
float *m,d;
/* Gán giá trị cho các biến cần thiết */
for(i=0; i<3*n; i++){
m[i]=p[i]+q[i]+s[i]+127+6*tan(d)-13*sin(d);
y=8*x + 128*z + 35*sqrt(d);
}

}

b. Đoạn mã sau thực hiện việc tính trung bình cộng các
phần tử trong mảng a[]. Phân tích, tìm các lỗi sai và
sửa lại (0.5đ):
double avg(double a[], int n){
int i;
double sum;
sum = 0.0;
for (i=0; isum += a[i];
return sum/n;

}


ĐÁP ÁN ĐỀ THI SỐ 2
Câu 1 (0.75đ): Ta có thể thấy ngay đoạn code này chẳng có gì ngoài việc trả về giá trị của subkey. Do đó việc tinh chỉnh
chỉ đơn giản là tinh chỉnh các biểu thức toán học sao cho máy có thể thực hiện nhanh hơn. Dễ dàng nhận thấy như sau:
Ở đây, giá trị bitoff đầu tiên dịch phải 5bit, sau đó lại được dịch trái 5bit. Như vậy biểu thức (bitoff>>3)<<3 sẽ chuyển
5bit cuối của bitoff thành 00000. Giả sử bitoff = ****..**abcde (abcde là các bit)
Do đó, bitoff - ((bitoff >> 5) << 5)) = ****..**abcde - ****..**00000 = 0000..00abcde
= bitoff & 0000..0011111 = bitoff & 0x1F

Vì vậy ta có đoạn code tối ưu như sau:
int calc(int subkey, int bitoff){
subkey >>= bitoff & 0x1F;
return subkey;
}

Câu 2 (1đ): Bài này khá đơn giản và quen thuộc:
Ở đây ban đầu p trỏ tới A[2].
b=*p++ ~ b=*p và p++ => b=A[2]=19, p trỏ tới A[3].
*p+=2*b => A[3]+=2*19 => A[3] = 39.
p++ => p trỏ tới A[4].
*p+=1 => A[4] +=1 => A[4] = 3.
Vậy giá trị các phần tử mảng sau khi thực hiện đoạn lệnh là A[] = {5,7,19,39,3,6}.
Câu 3 (2.5đ): Hàm đệ quy 1.25đ, hàm không đệ quy 1.25đ
//HÀM ĐỆ QUY
void chuyenCoSo(int base, int number)
{
char digits[]={'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
if(number<=0)

{
return;
}
//Gọi đệ quy hàm chuyenCoSo()
chuyenCoSo(base,number/base);
//Xuất kết quả ra màn hình
printf("%c",digits[number%base]);
}
//HÀM KHÔNG ĐỆ QUY
void chuyenCoSo_nonRec(int base, int number)
{
//Mảng chứa các chữ số của hệ 16 [hex]
char digits[]={'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};


char output[100];
int i=0;
while(number>0)
{
output[i++] = digits[number%base];
number = number/base;
}
i--;
//Xuất ra kết quả
for(;i>=0;i--)
{
printf("%c",output[i]);
}
}


Câu 4 (1đ): Xây dựng cấu trúc số phức như sau: (0.5đ)
typedef struct SoPhuc{
float Thuc, Ao;
}SP;

Đa năng hoá toán tử ~ có chức năng trả về số phức liên hợp với số phức ban đầu: (0.5đ)
SP operator ~ (SP &a){
a.Ao = - a.Ao;
return a;
}

Câu 5 (1đ): Tối ưu đoạn code…
+ Đặt các biểu thức bất biến là hằng số (0.25đ):
float temp1=127+6*tan(d)-13*sin(d);
Có thể một số bạn sẽ đặt thêm:
float temp2=35*sqrt(d); .Tuy nhiên biểu thức y = … không phụ thuộc vòng lặp for nên ta không cần dùng biến
temp2 mà chỉ cần bỏ biểu thức y = … ra khỏi vòng lặp for là xong.
+ Thay đổi các phép nhân với 2k bằng các phép dịch bit để tăng tốc độ tính toán (0.25đ):
8*x+128*z = x<<3 + z<<7
+ Nhìn vào vòng lặp for ta thấy cận trên và bước nhảy là 3*n, i++, tức là phải thực hiện 3*n lần lặp. Có thể tối ưu bằng
cách giảm bớt số lần lặp xuống còn n lần như sau: (kĩ thuật này còn gọi là kĩ thuật Loop Unrolling, các bạn có thể tìm
hiểu thêm trên Google) (0.5đ)
for(i=0; i<3*n; i+=3){
m[i]=p[i]+q[i]+s[i]+temp1;
m[i+1]=p[i+1]+q[i+1]+s[i+1]+temp1;
m[i+2]=p[i+2]+q[i+2]+s[i+2]+temp1;
}
Tổng hợp lại ta có đoạn code tối ưu hơn như sau:
int x,y,z,i,n,*p,*q,*s;



float *m,d;
/* Gán giá trị các biến cần thiết */
float temp1=127+6*tan(d)-13*sin(d);
y=x<<3 + z<<7 + 35*sqrt(d);
for(i=0; i<3*n; i+=3){
m[i]=p[i]+q[i]+s[i]+temp1;
m[i+1]=p[i+1]+q[i+1]+s[i+1]+temp1;
m[i+2]=p[i+2]+q[i+2]+s[i+2]+temp1;
}

Câu 6 (1đ): Thoạt nhìn đây có vẻ là một câu khá ‘hiểm’ và khó phát hiện ra lỗi. Tuy nhiên, nếu tinh ý thì câu này quá
đơn giản. Ở đây ta nhận thấy biến p cấp phát động sai do hàm strlen không tính kí tự kết thúc xâu (‘\0’). Do đó ta sửa lại
đoạn code như sau:
char *p, buf[256];
gets(buf);
p = malloc(strlen(buf)+1); //thay vì chỉ strlen(buf) như code gốc
strcpy(p, buf);

Hoặc:
char *p, buf[256];
gets(buf);
p = new char [strlen(buf)+1];
strcpy(p, buf);

Câu 7 (2đ): Đề yêu cầu đưa ra phần tử chẵn lớn nhất của danh sách. Sử dụng ý tưởng sau:
+ Duyệt toàn bộ danh sách và đếm số phần tử chẵn trong danh sách. Nếu không có phần tử chẵn thì trả về 0 ngay.
+ Nếu có phần tử chẵn thì lưu các phần tử chẵn này vào 1 mảng cấp phát động với kích thước = số phần tử chẵn (tiết
kiệm bộ nhớ)
+ Duyệt mảng này và tìm phần chẵn lớn nhất. Cái này thì quá đơn giản và quen thuộc.

//Hàm đếm số phần tử chẵn trong danh sách
int demSoPtChan(node *head){
int count=0;
node *tmp;
for(tmp=head; tmp!=NULL; tmp=tmp->next){
if (tmp->data % 2 == 0)
count++;
}
return count;
}
//Hàm tìm phần tử chẵn lớn nhất
int maxEven(node *head){
if (demSoPtChan(head) == 0) return 0;
else{
node *tmp;
int m=demSoPtChan(head);
int *a=new int [m];
int i=0, max;
for(tmp=head; tmp!=NULL; tmp=tmp->next){
if((tmp->data)%2==0)


a[i++]=tmp->data; //nếu gặp phần tử chẵn thì lưu vào mảng a
}
max=a[0];
for(i=0; iif (a[i]>max)
max=a[i];
}
return max;

}
}

Câu 8 (1đ):
a. Đoạn code này sẽ gặp vấn đề khi n=1. Cụ thể: với n=1 thì trong vòng lặp while, n=0, do đó fac = 0 => sai.
Sửa lại code như sau:
int factorial (int n){
int fac=1;
for( ; n>0; n--)
fac *= n;
return fac;
}

b. Đoạn code này chưa xét trường hợp mảng có 0 phần tử. Do đó chỉ cần sửa lại lệnh return như sau là ok.
return (n<=0) ? 0.0 : sum/n;


















×