Tổng số lượt xem trang

Thứ Bảy, 19 tháng 3, 2011

1 số vấn đề hay và hay nhầm lẫn trong lập trình C++ phần 2


1 số vấn đề hay và hay nhầm lẫn trong lập trình C++
phần 2
IV. Nhầm lẫn kết quả và vùng nhớ
Cho đoạn chương trình sau:

#include<iostream.h>

void f(int* &y)
{
     int z = 101;
     y = &z;
}

int main()
{
     int a=1;
     int* b;
     b=&a;
     f(b);

     cout << *b << endl;
     cout << *b << endl;

     return 0;
}

Chú ý 2 dòng cout liên tiếp.
Khi chạy chương trình, màn hình lại hiện:

101
4198768

Tại sao lại như vậy?

Giải đáp:

Trên lý thuyết, hàm f() có công dụng là thay đổi giá trị con trỏ y. Tức là thay đổi vùng nhớ mà y trỏ tới thành z , chính là 1 biến local mang giá trị 101. Theo lí thuyết khi ra khỏi hàm f thì z sẽ bị hủy đi. Vậy nên sau khi ta gọi f(b) thì b sẽ trỏ đến 1 vùng nhớ có trị rác. Và khi ta xuất *b thì chắc chắn ra trị rác. Vậy tại sao lần cout thứ nhất, nó vẫn ra giá trị 101?
Xem hình minh họa sau:

























































Hãy tưởng tượng với đoạn code trên, lần đầu tiên ta xuất ra được 101, ta ngộ nhận ta đã đúng và ta lại tiếp tục dùng số liệu đó xử lý tiếp thì hậu quả khó mà đoán được.
Nhiều khi ta viết 1 chương trình, kết quả ta tưởng chừng là đúng, nhưng bản chất chưa thật sự đúng

Thứ Sáu, 18 tháng 3, 2011

Suy nghĩ và phân tích bài tập thuật giải buổi 2


Hướng dẫn code BFS
I/ Cấu trúc dữ liệu:
-       Dùng 1 danh sách kề để biểu diễn 1 đồ thị không trọng số
-       Dùng 1 hàng đợi để duy trì trật tự tìm kiếm theo chiều rộng
-       Dùng 1 mảng màu để không lặp lại các đỉnh tìm kiếm
-       Dùng 1 mảng để xác định đường đi ngắn nhất từ đỉnh nguồn s đến các đỉnh đã được tìm kiếm
-       Dùng 1 mảng để lưu trữ đỉnh đi trước của đỉnh được tìm kiếm

Vậy điều ta cần làm trước tiên là tổ chức và cài đặt : 1 danh sách kề, 1 queue và 3 array.

1/ Danh sách kề
Thành phần: 1 array, mỗi phần tử trong mảng bắt đầu 1 danh sách liên kết,( giống giống bảng băm ).
Thao tác:
-       Danh sách liên kết cần các thao tác: khởi tạo rỗng, thêm 1 phần tử vào danh sách liên kết (nên là thêm cuối), xuất(để kiểm tra).
-       Khởi tạo đồ thị rỗng, nhập đồ thị, xuất đồ thị(để kiểm tra).

2/ Hàng đợi:
Có 2 cách tổ chức là danh sách đặc hoặc danh sách liên kết, cả 2 cách đều có ưu điểm riêng.
-       Danh sách đặc thì cài đặt dễ, truy xuất nhanh.
-       Danh sách liên kết thì vì ta đã cài đặt danh sách liên kết ở danh sách kề phía trên rồi, chỉ thêm 1 số thao tác . Nhưng nếu trước đó ta cài danh sách liên kết gồm 2 con trỏ thành phần (trỏ vào đầu và cuối danh sách) thì sẽ không cần duyệt tuần tự, kết quả là truy xuất nhanh.
Thành phần: danh sách, 2 biến vị trí đầu và cuối danh sách.
Thao tác: khởi tạo rỗng, xét xem rỗng hay không, đưa 1 phần tử vào hàng đợi, lấy 1 phần tử ra hàng đợi.

3/ Mảng màu:
Kiểu của mảng màu phụ thuộc vào kiểu của màu. Kiểu của màu do người lập trình chọn, để đơn giản có thể là kiểu nguyên hay kiểu kí tự. Việc quy ước màu nào là chữ gì số mấy cũng là do người lập trình. Ngoài ra, ta cũng có thể dùng từ khóa define để black, gray, white đại diện cho các số 2, 1, 0 để dễ và tiện trong cài đặt.

4/ Mảng đường đi:
Lưu số cạnh ít nhất từ đỉnh nguồn s đến các đỉnh đã tìm kiếm dĩ nhiên là kiểu số nguyên

5/ Mảng lưu trữ đỉnh đi trước của đỉnh được tìm kiếm có kiểu giống với kiểu của đỉnh đồ thị
Nếu ta tổ chức đỉnh của đồ thị kiểu số nguyên thì mảng lưu trữ đỉnh sẽ kiểu số nguyên
Ví dụ về thành phần cấu trúc:

struct Node{
          int info;
          Node* next;
};

typedef Node* StNode;

struct GRAPH{
          StNode list[SIZE];
          int n;//the number of vertices
};

typedef int DATA;

struct QUEUE
{
          DATA array[SIZE];
          int front;
          int rear;
};

#define white 0
#define gray 1
#define black 2



Cuối cùng ta khai báo:
1 đồ thị G                  GRAPH G;
1 hàng đợi Q              QUEUE Q;
1 mảng color              int color[SIZE];     
1 mảng d                   int d[SIZE]; 
1 mảng pi                  int pi[SIZE];




II/ Thuật giải
Bắt đầu cài đặt dựa vào mã giả.
Đây là mã giả và mã C++ của mình tương ứng. Vì tổ chức còn dở nên chưa sát với mã giả.

Mã giả:                                                              Mã C++:

BFS(G, s)                                               void BFS(GRAPH G,int s)
                                                            {
                                                                   int u,v;
                                                                   StNode p;
 1  for each vertex u thuộc [G] - {s}                  for(int i=0; i<G.n ; i++)
                                                                   {
 2       do color[u] WHITE                                       color[i] = white;
 3           d[u]                                                   d[i] = -1;
 4          pi[u] NIL                                                 pi[i] = -1;
                                                                   }
 5  color[s] GRAY                                         color[s] = gray;
 6  d[s] 0                                                    d[s] = 0;
 7  pi[s] NIL                                                pi[s] = -1;
 8  Q Ø                                                      emptyqueue(Q);
 9  ENQUEUE(Q, s)                                          enqueue(Q,s);
10  while Q ≠ Ø                                            while(!isemptyqueue(Q))
                                                                   {
11      do u DEQUEUE(Q)                                        dequeue(Q,u);
12         for each v thuộc Adj[u]                                 for(p=G.list[u];p!=NULL;p=p->next)
                                                                             {
                                                                                      v = p->info;
13             do if color[v] = WHITE                                      if(color[v]==white)  
14                   then color[v] GRAY                                            color[v] = gray;
15                        d[v] d[u] + 1                                                d[v] = d[u]+1;
16                        pi[v] u                                                         pi[v] = u;
17                        ENQUEUE(Q, v)                                                enqueue(Q,v);
                                                                             }
18         color[u] BLACK                                         color[u] = black;
                                                                   }
                                                            }

Kết quả của BFS chính là 2 mảng pi và d. Trong đó mảng pi sẽ là dữ liệu quan trọng trong ứng dụng BFS cho bài toán tìm đường đi ngắn nhất.

Về hàm main thì cũng sẽ nhập đồ thị à BFS() à xuất pi và d ra để kiểm tra xem đúng chưa. Vậy thôi. :P

Thứ Sáu, 4 tháng 3, 2011

Suy nghĩ và phân tích bài tập thuật giải buổi 1



THUẬT GIẢI
 Đồ thị - buổi 1

Suy nghĩ và phân tích bài tập thuật giải buổi 1


Đề bài tập buổi 1: Viết khai báo cấu trúc, thủ tục xuất nhập trên đồ thị trong các trường hợp sau:
1/ Biểu diễn đồ thị bằng danh sách kề
2/ Biểu diễn đồ thị bằng ma trận kề
3/ Biểu diễn đồ thị có trọng số bằng danh sách kề
4/ Biểu diễn đồ thị có trọng số bằng ma trận kề
5/ Biểu diễn đồ thị vô hướng bằng ma trận kề

1/Tổ chức bằng danh sách kề
Khai báo cấu trúc:
- 1 đồ thị bao gồm:
+       1 danh sách đặc, mỗi phần tử là phần tử đầu tiên của 1 danh sách liên kết đơn. 1 phần tử trong danh sách liên kết gồm 2 thông tin (đỉnh kề và địa chỉ phần tử kế tiếp).
+       Số đỉnh (cũng tức là kích thước của danh sách đặc phía trên).
Thủ tục nhập:
- Trước tiên, về hình thức thủ tục nhập có 2 cách để hiện thực: nhập từ bàn phím và nhập từ tập tin. Điểm khác biệt rõ nhất chính là cửa sổ chú ý. 1 bên có thể là cửa sổ console, 1 bên có thể là cửa sổ notepad. Hãy cùng phân tích ưu và nhược của 2 cách nhập trên. Nếu lập trình thủ tục nhập từ bàn phím thì khi gọi thủ tục người lập trình có thể xuất kèm theo các câu hướng dẫn, các lệnh rẽ nhánh để hướng dẫn người dùng nhập đúng chỗ. Mặt khác, nhập từ tập tin lại khó có thể làm được điều đó, chắc chắn người dùng sẽ phải đọc kĩ cách sử dụng để có thể đưa dữ liệu vào đúng thứ tự, vị trí trong tập tin, nhưng bù lại độ chính xác, tốc độ nhập nhanh hơn tuyệt đối, tiết kiệm thời gian rất nhiều, nhất là khi kiểm thử.
- Tiếp theo đến nội dung nhập, các đỉnh trong đồ thị thường được đánh số tự nhiên bắt đầu từ số 1. Rất nhiều mã giả, tài liệu đều nhất trí đỉnh đồ thị bắt đầu từ số 1. Người dùng khi nhập cũng sẽ nhập đỉnh đồ thị bắt đầu từ số 1. Nếu chúng ta hiện thực bằng ngôn ngữ lập trình C++ thì cũng sẽ có 1 trong 2 lựa chọn:
+       1 là ta lập trình mà bỏ qua phần tử thứ 0 trong danh sách đặc, ưu điểm là nó sẽ khớp với mã giả, dẫn đến hiện thực dễ và nhanh, ít sai. Thống nhất dữ liệu xuất nhập phía người dùng và dữ liệu trong chương trình. Ta chỉ chịu hao phí phần tử thứ 0 đó.
+       2 là mình chịu khó viết lệch mã giả, phần tử thứ 0 đại diện đỉnh số 1, viết thêm các lệnh xử lý xuất nhập, để khi ở phía người dùng: đồ thị bắt đầu bằng đỉnh số 1 và trong lập trình đồ thị bắt đầu bằng đỉnh số 0. Việc này không chỉ đơn thuần là -1 khi nhập và +1 khi xuất mà còn ảnh hưởng đến việc chọn cách quy ước giá trị các trường hợp (thấy rõ hơn trong ma trận kề).
- Sau khi định rõ, thủ tục nhập sẽ được viết theo trình tự sau:
+       Khởi tạo rỗng cho các danh sách liên kết.
+       Đọc dữ liệu, đầu tiên phải là số lượng đỉnh trong đồ thị.
+       Đọc các tập cạnh kề của các đỉnh, kết hợp đưa vào danh sách liên kết các đỉnh tương ứng bằng thủ tục thêm 1 phần tử vào danh sách liên kết (thêm đầu cho dễ, mặc dù thứ tự sẽ bị ngược).
 Thủ tục xuất:
- Hình thức và nội dung tương tự như thủ tục nhập.
- Đơn giản là duyệt từng danh sách liên kết và cho xuất các đỉnh kề.


2/Tổ chức bằng ma trận kề
Khai báo cấu trúc:
- 1 đồ thị bao gồm:
+       1 ma trận.
+       Số đỉnh (cũng tức là kích thước của danh sách đặc phía trên).
Thủ tục nhập và xuất:
- Hình thức và nội dung tương tự như đối với danh sách kề.
- Ta có thể không cần đến hàm khởi tạo. Việc còn lại chỉ là viết xuất nhập ma trận.


  •                 Ví dụ các hình thức xuất nhập đối với đồ thị:



Ma trận kề:

<số đỉnh>
<ma trận>



4
0 0 1 1
1 0 0 0
0 1 0 1
0 0 0 0


-----------------------

Danh sách kề:


<số đỉnh>
<số đỉnh kề với đỉnh 1><tập các đỉnh kề đó>
<số đỉnh kề với đỉnh 2><tập các đỉnh kề đó>
….
4
2 3 4
1 1
2 2 4
0

------------------------

Danh sách cạnh:


<số đỉnh>
<số cạnh>
<cạnh thứ 1>
<cạnh thứ 2>


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


------------------------


Còn tiếp…