4. Thiết kế hệ thống mô phỏng một số thuật toán trên đồ thị
4.1. Lựa chọn công cụ lập trình
Trong luận văn này, chúng tôi không xem xét ngôn ngữ C# một cách tách biệt, nó luôn đồng hành với "Bộ khung .NET". C# là một trình biên dịch hướng .NET, nghĩa là tất cả các mã của C# luôn luôn chạy trên trên môi trường .NET Framework. Điều đó dẫn đến 2 hệ quả sau:
Cấu trúc và các lập luận C# được phản ánh các phương pháp luận của .NET ngầm bên dưới. Trong nhiều trường hợp, các đặc trưng của C# thậm chí được quyết định dựa vào các đặc trưng của .NET, hoặc thư viện lớp cơ sở của .NET. Trong Microsoft Intermediate Language (thường được viết tắt là "Intermediate Language", hay "IL") tương tự như ý tưởng về mã Java byte, nó là một ngôn ngữ cấp thấp với những cú pháp đơn giản (dựa trên cơ sở mã số hơn là text), chính điều này sẽ làm cho quá trình dịch sang mã máy nhanh hơn. Hiểu kĩ các cú pháp này sẽ mang lại những lợi ích đáng kể.
Hƣớng đối tƣợng
Dữ liệu mẫu đưa vào chương trình được mô phỏng bằng đồ họa: - Dữ liệu mẫu
- Dữ liệu trực tiếp - Mô phỏng theo từng bước
- Mô phỏng tự động toàn bộ thuật toán
- Đồ thị đã được mô phỏng bằng đồ họa: những thay đổi trên hình vẽ qua các bước thực thi thuật toán.
Input Thuật toán Output
Như mọi ngôn ngữ lập trình hướng đối tượng khác, C# có các tính năng đóng kín, kế thừa và đa hình. Mỗi lớp của C# bao gồm các trường và phương thức tương ứng. Trong đó:
Trường: là dữ liệu chỉ trạng thái của đối tượng.
Phương thức: là các khả năng của đối tượng trả lời các tác động đến nó.
Độc lập nền
Trước tiên, độc lập nền có nghĩa là các file chứa mã lệnh có thể chạy trên bất kì nền nào, vào thời gian chạy trình biên dịch cuối sẽ hoạt động và mã có thể chạy trên một nền cụ thể. Nói cách khác việc dịch mã nguồn sang Intermediate Language cho phép độc lập nền trong .NET, nó giống như cách dịch mã nguồn sang Java byte code cung cấp sự độc lập nền trong Java.
Sự cải tiến trong thực thi
Mặc dù chúng ta đã so sánh với Java, IL thật sự có một chút khả quan hơn Java. IL luôn là trình biên dịch mạnh, ngược lại Java byte code thì thường là thông dịch. Một trong những bất lợi của Java là vào lúc thực thi quá trình dịch từ java byte code sang mã máy tốn nhiều tài nguyên.
Thay vì phải dịch toàn bộ ứng dụng một lần, trình biên dịch JIT sẽ biên dịch từng phần mã khi nó được gọi. Khi mã được dịch rồi, mã kết quả sẽ được giữ lại cho tới khi thoát khỏi ứng dụng, chính vì thế nó không phải biên dịch lại trong lần chạy kế tiếp. Microsoft quả quyết rằng cách xử lí này có hiệu lực cao hơn là dịch toàn bộ ứng dụng, bởi vì có trường hợp một khối lượng lớn mã của ứng dụng không bao giờ được sử dụng trong thời gian chạy. Khi sử dụng trình biên dịch JIT, các đoạn mã này sẽ không bao giờ được dịch.
Chính vì thế nhà cung cấp hi vọng rằng mã IL sẽ thực thi nhanh như là mã máy. Lời lí giải là, lần dịch cuối cùng trong thời gian chạy, trình biên dịch JIT sẽ biết chính xác loại vi xử lí mà chương trình sẽ chạy. Có nghĩa là nó có thể tối ưu
mã thi hành cuối cùng bằng cách tham chiếu đến các đặc trưng của từng các bộ lệnh ứng với các loại vi xử lí đó. Trình biên dịch JIT có thể thực hiện tối ưu giống như Visual Studio 6, ngoài ra nó còn có thể tối ưu cho các loại vi xử lí cụ thể mà mã chương trình sẽ chạy.
Tƣơng hoạt giữa các ngôn ngữ
Chúng ta đã biết cách thức IL cho phép độc lập nền, trình biên dịch JIT có thể cải thiện quá trình thực thi. Tuy nhiên, IL cũng làm cho tương hoạt giữa các ngôn ngữ trở nên dễ dàng hơn. Bạn có thể biên dịch IL từ một ngôn, và mã này sau đó có thể tương hoạt với IL được biên dịch bởi một ngôn ngữ khác.
Bảo mật và hiệu quả cao
C# là một thành phần của bộ Visual Studio .NET dành cho lập trình môi trường mạng nên nó có khả năng bảo mật cao.
Cấu trúc câu lệnh khá đơn giản, khả chuyển, giao diện đồ họa, dễ sử dụng. Làm được tất cả những gì Java có thể làm được.
4.2. Chức năng mô phỏng của các thuật toán đƣợc cài đặt 4.2.1 Mô phỏng thuật toán tìm kiếm
Khung chương trình cho phép người dùng nhập đồ thị để mô phỏng: tạo một đồ thị mới, tạo một đồ thị đã chuẩn bị từ trước:
Màn hình mô phỏng được chia thành 3 phần. Phần giả mã, phần trạng thái của các đối tượng trong quá trình thực hiện thuật toán và phần hình ảnh đồ thị. Người dùng có thể lựa chọn thực hiện thuật toán trên đồ thị có hướng hoặc vô hướng. Chương trình mô phỏng cố gắng làm rõ cách thức hoạt động của thuật toán theo từng bước giã mã ở trên theo cách:
Công cụ để tạo đồ thị cho mô phỏng Khung giả mã Khung mô phỏng bằng hình ảnh đồ thị Khung trạng thái
Tại khung giả mã: Chứa đoạn giả mã của thuật toán tìm kiếm tương ứng với lựa chọn của người dùng. Thuật toán thực hiện đến bước nào thì bước đó đổi màu cho người dùng tiện quan sát.
Tại khung trạng thái: Ghi nhận và hiển thị những thay đổi của tập các đỉnh đã được thăm, đỉnh đang được xét….
Tại khung đồ thị: Hình ảnh đồ thị sẽ thay đổi theo mỗi bước thuật toán thực hiện qua. Các đỉnh đã được thăm qua sẽ được tô màu vàng. Các cạnh trên đường đi tìm thấy sẽ được tô màu đỏ để người học quan sát kết quả một cách trực quan.
4.2.2. Mô phỏng thuật toán Dijkstra
Khung chương trình cho phép người dùng nhập đồ thị để mô phỏng:
Việc mô phỏng thuật toán Dijkstra trong chương trình cho phép người dùng có thể lựa chọn trên 2 loại đồ thị: Đồ thị vô hướng có trọng số hoặc đồ thị có hướng có trọng số. Cũng giống như việc mô phỏng cho bài toán tìm kiếm, màn hình mô phỏng được chia thành ba phần:
- Khung giả mã: Mô tả thuật toán Dijkstra bằng giả mã dạng liệt kê. Thuật toán thực hiện đến bước nào thì bước đó đổi màu cho người dùng tiện quan sát.
- Khung trạng thái: Ghi nhận và hiển thị những thay đổi của tập các đỉnh đã được thăm, đỉnh đang được sửa nhãn, đỉnh mới kết nạp vào tập các đỉnh đã được tối ưu nhãn….
- Khung đồ thị: Hình ảnh đồ thị sẽ thay đổi theo mỗi bước thuật toán thực hiện qua. Các đỉnh đã được thăm qua sẽ được tô màu vàng, các giá trị đã tối ưu thể hiện chi phí ngắn nhất từ đỉnh xuất phát đến các đỉnh trung gian được tô màu đỏ. Các cạnh trên đường đi tìm thấy sẽ được tô màu đỏ hoặc một thông báo không tìm thấy đường đi từ đỉnh xuất phát đến đỉnh đích để người học quan sát kết quả một cách trực quan. Khung giả mã của thuật toán Khung mô phỏng trên đồ thị Khung trạng thái của thuật toán
4.2.3. Mô phỏng thuật toán Ford – Bellman
Vì thuật toán Ford – Bellman cũng là thuật toán tìm đường đi ngắn nhất giữa hai cặp đỉnh của đồ thị có trọng số giống như Dijkstra nên dưới đây, chúng tôi chỉ xin giới thiệu về màn hình là việc của chương trình (còn ý nghĩa các khung làm việc giống hệt thuật toán Dijkstra.
4.2.4. Mô phỏng thuật toán Prim
Do thuật toán Prim tìm cây khung nhỏ nhất chỉ được thực hiện trên đồ thị vô hướng có trọng số nên trong chương trình mô phỏng người dùng không có các lựa chọn như các thuật toán trên, chỉ có thể nhập cho đồ thị vô hướng có trọng số. Khung chương trình mô phỏng cũng tương tự như các thuật toán đã mô tả ở trên.
Khung chương trình để người dùng nhập dữ liệu đầu vào:
Khung giả mã của thuật toán Khung mô phỏng trên đồ thị Khung trạng thái của thuật toán
Khung chương trình mô phỏng:
Tại khung giả mã: Chứa đoạn giả mã của thuật toán Prim tương ứng với lựa chọn của người dùng. Thuật toán thực hiện đến bước nào thì bước đó đổi màu cho người dùng tiện quan sát.
Tại khung trạng thái: Ghi nhận và hiển thị những thay đổi của tập các đỉnh đã được thăm, đỉnh đang được xét….
Khung giả mã Khung mô phỏng bằng hình ảnh đồ họa Khung trạng thái
Tại khung đồ thị: Hình ảnh đồ thị sẽ thay đổi theo mỗi bước thuật toán thực hiện qua. Các đỉnh đã được thăm qua sẽ được tô màu vàng. Các đỉnh đã được kết nạp vào cây và các cạnh thuộc sẽ được tô màu đỏ để người học quan sát kết quả một cách trực quan.
4.2.5. Mô phỏng thuật toán Kruskal
Do thuật toán Kruskal tìm cây khung nhỏ nhất chỉ được thực hiện trên đồ thị vô hướng có trọng số nên trong chương trình mô phỏng người dùng không có các lựa chọn như các thuật toán trên, chỉ có thể nhập cho đồ thị vô hướng có trọng số. Khung chương trình mô phỏng cũng tương tự như các thuật toán đã mô tả ở trên.
Khung chương trình để người dùng nhập dữ liệu đầu vào:
Tại khung giả mã: Chứa đoạn giả mã của thuật toán Kruskal tương ứng với lựa chọn của người dùng. Thuật toán thực hiện đến bước nào thì bước đó đổi màu cho người dùng tiện quan sát.
Tại khung trạng thái: Ghi nhận và hiển thị những thay đổi của tập các đỉnh đã được thăm, đỉnh đang được xét….
Tại khung đồ thị: Hình ảnh đồ thị sẽ thay đổi theo mỗi bước thuật toán thực hiện qua. Các đỉnh đã được thăm qua sẽ được tô màu vàng. Các đỉnh đã được kết nạp vào cây và các cạnh thuộc sẽ được tô màu đỏ để người học quan sát kết quả một cách trực quan.
4.2.6. Thuật toán tìm chu trình Hamilton
5. Giới thiệu chương trình
5.1.Tổng quan về hệ thống
Hệ thống mô phỏng được chia thành các module nhỏ. Mỗi module thực hiện một chức năng riêng biệt. Có 2 module chính:
Module GraphTool: Thực hiện công việc thiết kế giao diện dành cho quá trình mô phỏng. Từ việc nhập đồ thị mới, người dùng lựa chọn thuật toán mô phỏng và mô phỏng theo kịch bản đã được “dựng” sẵn.
Khung giả mã Khung mô phỏng bằng hình ảnh đồ họa Khung trạng thái
Module Model: Thực hiện cài đặt các mô hình thuật toán, lưu trữ các thông tin cần thiết và lên kịch bản cho quá trình mô phỏng.
Giữa hai module này luôn có mối quan hệ khăng khít với nhau. Một module thực thi thuật toán do người dùng lựa chọn sau đó lên kịch bản để mô phỏng. Module còn lại tiếp nhận kịch bản từ module kia và mô phỏng theo kịch bản đã được dựng sẵn theo đúng mô hình thuật toán đã được thực thi để trình chiếu tới người học.
Để việc mô phỏng có thể áp dụng được với nhiều thuật toán khác nhau, tại mỗi module, việc cài đặt các công cụ hỗ trợ cho quá trình mô phỏng và dựng kịch bản mô phỏng chúng tôi luôn dựng ở dạng tổng quát. Thêm vào đó là một số công cụ riêng rẽ, thể hiện đặc trưng của mỗi thuật toán. Dưới đây là một số đối tượng dùng chung cho quá trình mô phỏng:
5.1.1. Các đối tƣợng xây dựng cấu trúc đồ thị
Entity: chứa 3 thuộc tính:
+ Key: tên của đỉnh trong đồ thị
+ Value: Trọng số của cạnh (trong trường hợp đồ thị là vô hướng thì trọng số = 1 nghĩa là 2 đỉnh có cạnh nối, = 0 nghĩa là không có cạnh nối)
+ IsDirection: đánh dấu đồ thị ban đầu là có hướng hay vô hướng. public class Entity
{
public virtual string Key { get; set; }
public virtual int Value { get; set; }
public static bool IsDirection {get; set;} Đối tượng Graph: gồm các thuộc tính:
+ Tập các đỉnh Vertexs + Tập các cạnh.
public class Graph {
public Graph() {
Vertexs = new HashSet<Vertex>(); }
public HashSet<Vertex> Vertexs { get; set; }
public int GetEdgeValue(string fromKey, string toKey) {
Vertex from = GetVertex(fromKey); if (from.ConnectTo(toKey))
{
return from.GetEdge(fromKey, toKey).Value; }
return int.MaxValue; }
public Vertex GetVertex(string key) {
foreach (Vertex v in Vertexs) {
if (v.Key == key) return v; }
throw new Exception("Key is not exist."); }
}
Vertex: Chứa các thuộc tính về đỉnh + Kế thừa các thuộc tính của lớp Entity + Phương thức:
ConnectTo: xác định đỉnh kề với đỉnh đang xét
NextToVertex: Xác định tập các đỉnh kề với đỉnh đang xét. AddEdge: Thêm 1 cạnh (cung) có 1 đầu mút là đỉnh đang xét.
RemoveEdge: Loại bỏ 1 cạnh (cung) có 1 đầu mút là đỉnh đang xét. public class Vertex : Entity
{
private Dictionary<string, Edge> _edges = new Dictionary<string, Edge>();
private IList<string> _nextToVertexs = new List<string>();
public bool ConnectTo(string keyTo) {
string edgeKey = this.Key + ">" + keyTo;
if (_edges.ContainsKey(edgeKey)) return true;
if (Entity.IsDirection == false) {
edgeKey = keyTo + ">" + this.Key;
if (_edges.ContainsKey(edgeKey)) return true;
}
return false; }
public void AddEdge(Edge edge) { _edges.Add(edge.Key, edge); if (edge.FromVertexKey != Key) { _nextToVertexs.Add(edge.FromVertexKey); }
else if (edge.ToVertexKey != Key) {
_nextToVertexs.Add(edge.ToVertexKey); }
}
public void RemoveEdge(Edge edge) { _edges.Remove(edge.Key); if (edge.FromVertexKey != Key) { _nextToVertexs.Remove(edge.FromVertexKey); }
else if (edge.ToVertexKey != Key) {
_nextToVertexs.Remove(edge.ToVertexKey); }
}
public void RemoveAllEdges() {
_edges.Clear(); }
public Edge GetEdge(string fromKey, string toKey) {
if (string.IsNullOrEmpty(fromKey) || string.IsNullOrEmpty(toKey))
throw new ArgumentNullException("Key is not alowed null."); string edgeKey = fromKey + ">" + toKey;
if (_edges.ContainsKey(edgeKey)) return _edges[edgeKey];
else if (Entity.IsDirection == false) {
edgeKey = toKey + ">" + fromKey; return _edges[edgeKey];
}
throw new Exception("Not found edge"); }
public IList<string> GetNextToVertexs() {
return _nextToVertexs; }
Edge: chứa các thuộc tính về đỉnh thừa kế từ lớp Entity bao gồm 2 thông số: + Cạnh được nối từ FromVertexKey đến ToVertexKey.
+ Trọng số của cạnh (trong trường hợp đồ thị không trọng số thì ta dùng giá trị này để đánh dấu có cạnh nối giữa 2 đỉnh hay không).
public class Edge : Entity {
public override string Key {
get {
return FromVertexKey + ">" + ToVertexKey; } set { base.Key = value; } }
public string FromVertexKey { get; set; }
public string ToVertexKey { get; set; } }
Một cái “túi” để “đựng” các bước thực hiện theo thuật toán để mô phỏng. public class BagStep
{
private IList<Step> _steps = new List<Step>();
public void AddStep(Step step) {
_steps.Add(step); }
public IList<Step> Steps {
get { return _steps; } }
}
5.1.2. Công cụ vẽ hình ảnh để mô phỏng
CanvasDrawing: Xây dựng toàn bộ đồ thị nền trước, trong và sau khi mô phỏng.
EdgeDrawing: vẽ cạnh trước, trong và sau khi thực hiện thuật toán mô phỏng.
EntityDrawing: Chuyển đổi 2 chế độ: chế độ cho phép người dùng nhập một đồ thị đầu vào và chế độ mô phỏng.
VertexDrawing: vẽ đỉnh trước, trong và sau khi thực hiện thuật toán mô phỏng, những thay đổi giá trị trên các đỉnh cũng được thể hiện trên hình vẽ.
5.1.3. Chức năng chi tiết của các công cụ hỗ trợ cho quá trình mô phỏng
public void AddEdgeDrawing(string
fromVertex, string toVertex, int
value) Vẽ một cạnh
private void
RemoveVertexDrawing(VertexDrawing
vertexDrawing){} Vẽ đỉnh
public bool IsInShortestPath { get;
set; } Chứa đựng thông tin về các cạnh trên
đường đi ngắn nhất của thuật toán Dijkstra
public bool IsInSpanningTree { get;
set; } Chứa đựng thông tin về các cạnh thuộc
cây khung của thuật toán Prim
public bool IsInFindingPath { get;
set; } Chứa đựng thông tin về các cạnh trên
đường đi từ đỉnh xuất phát đến đỉnh kết thúc của thuật toán tìm kiếm DFS và BFS
private void
DrawAtDesignMode(DrawingContext
drawingContext)
Chế độ đồ họa trong khi xây dựng dữ liệu đầu vào
private void
DrawAtRunMode(DrawingContext
drawingContext)
Chế độ đồ họa trong khi thực hiện mô phỏng trên đồ thị đã cho
public void
AddEdgeDrawing(EdgeDrawing e) {}
Các công cụ hỗ trợ quá trình thiết kế dữ liệu đầu vào bằng hình vẽ và quá trình mô phỏng.
public void
RemoveEdgeDrawing(EdgeDrawing e) {}
public void ReRender(){}
public void
RemoveAllEdgeDrawings(){}
5.2. Giới thiệu các công cụ hỗ trợ mô phỏng do ngƣời dùng cài đặt
Như đã nói ở trên, chúng tôi dùng C# là ngôn ngữ cài đặt chương trình mô phỏng là để tận dụng các ưu điểm của nó. Việc mô phỏng trong chương trình được chia thành các module, các chức năng hỗ trợ việc mô phỏng lại được chia thành các module nhỏ hơn, đóng kín với các module khác. Các lớp độc lập nhau