Cong ty Cong Nghe Tin hoc Nha truong http://www.vnschool.net
Lịch sử của các loại cấu trúc dữ liệu điển hình
01/11/2007

Công thức “Data Structure + Algorithm = Program” đã quá quen thuộc và trở thành cẩm nang cho các lập trình viên từ thời kỳ lập trình mã máy (Assembly, …) cho đến khi các công nghệ hướng đối tượng (object-oriented programming ) và lập trình trực quan (visual programming) nắm giữ tư tưởng chủ đạo của lập trình. Bài viết đóng góp sự hiểu biết về lịch sử ra đời và phát triển của các loại cấu trúc dữ liệu điển hình từ các loại danh sách (lists) cho đến cấu trúc dữ liệu dạng cây (trees) .

Hy vọng bạn học hiểu phần nào nguyên do và những động lực, cũng như những cái tên giúp khai sinh ra một trong những thành phần quan trọng nhất của máy tính nói chung và lập trình nói riêng.

Nguồn gốc sự ra đời của danh sách

Danh sách tuyến tính (Linear List) và các mảng (rectangular arrays)4 để lưu trữ thông tin ở các vị trí bộ nhớ liên tiếp (memory locations) đã được sử dụng từ những ngày đầu tiên của máy tính có khả năng lưu trữ chương trình (stored-program computers) hay trong những chương trình cơ bản đầu tiên để duyệt (traverse) các cấu trúc này - phép toán cơ bản nhất và buộc phải có trong bất kỳ loại cấu trúc dữ liệu nào. Ví dụ như các công trình của các nhà khoa học máy tính nổi tiếng như J.von Neumann4 vào năm 1946, hay Wilkes4 vào năm 1951 đặc biệt là của Konrad Zuse vào năm 1945. Trong công trình của mình, Zuse là người đầu tiên phát triển các thuật toán quan trọng làm việc với các danh sách có độ dài thay đổi (varying lengths). Trước khi các phép toán đánh chỉ số (indexing) ra đời, các phép toán trên danh sách tuần tự (sequential list) được thực hiện bằng chính các lệnh (instructions) trên ngôn ngữ máy, và những yêu cầu các phép toán số học như vậy là một trong những động lực tạo ra các máy tính có các chương trình chia sẻ bộ nhớ chứa dữ liệu mà chúng phải xử lý.

Các kỹ thuật cho phép các danh sách tuyến tính với chiều dài thay đổi chia sẻ các vị trí bộ nhớ tuần tự, bằng cách chuyển chúng về phía sau hoặc trước tù trường hợp, điều này có ảnh hưởng trong các sự phát triển về sau của cấu trúc dữ liệu trong máy tính. Dunlap của hãng điện tử Digitek5 phát triển các kỹ thuật này trước năm 1963 để thiết kế một loạt các chương trình dịch (compiler program), tại cùng thời điểm với phát minh độc lập về bộ chương trình dịch COBOL6 nổi tiếng của tập đoàn IBM và một loạt các chương trình liên quan có tên là CITRUS. Các kỹ thuật này chưa được công bố cho tới sau khi chúng được phát triển độc lập trong phần mềm ALGOL bởi Jan Garwick của Bộ quốc phòng Na Uy vào năm 1964.

Ý tưởng lưu các danh sách tuyến tính tại các vị trí bộ nhớ không liên tục dường như có liên hệ với việc thiết kế các máy tính với các bộ nhớ kiểu “rotating drum memories”, xuất hiện trong các máy IBM 650 - một trong các máy tính ra đời sớm nhất trên thế giới. “Drum memory” (hình bên) là bộ nhớ cổ nhất của máy tính được phát minh vào năm 1932 tại Áo, và sử dụng rộng rãi trong hai thập kỷ 50s và 60s. Một drum là bộ nhớ làm việc chính của máy tính, với dữ liệu và chương trình ở dạng “on/off” sử dụng các băng từ (tape) và thẻ khoan (punch cards), về sau được thay bằng mạch bán dẫn (semiconductor) như ngày nay. Với các máy tính này, sau khi thực hiện lệnh ở vị trí n, máy tính thường không sẵn sàng thực hiện lệnh ở vị trí n + 1 vì drum lúc này đã quay (rotate) qua điểm đó. Như vậy do thiết kế, tùy thuộc vào các lệnh thực hiện, vị trí bộ nhớ thực hiện lệnh tiếp theo thường là n + 7 hoặc n + 18, và máy tính thường thực thi nhanh gấp 6, 7 lần nếu các lệnh được sắp xếp một cách tối ưu thay vì đặt liên tục nhau trong bộ nhớ. (Bài toán thú vị về vị trí tối ưu của các lệnh đã được bàn luận khá kỹ trên tạp chí nổi tiếng ACM của hiệp hội máy tính Hoa Kỳ số 8 vào năm 1961). Và do đó, một trường địa chỉ phụ (extra address field) được thêm vào mỗi lệnh ngôn ngữ máy để lưu kết nối tới lệnh tiếp theo. Ý tưởng này gọi là “one-plus-one addressing” đưa ra bởi John Mauchly trong tạp chí Lý thuyếtKỹ thuật thiết kế máy tính điện tử số 4 vào năm 1946. Trong đó có nói tới cả ký hiệu của danh sách liên kết (linked list) mặc dù các phép toán chèn và xóa quan trọng sau này thường xuyên được sử dụng thời điểm này vẫn chưa được biết tới. Danh sách cũng sớm xuất hiện trong các chương trình máy tính của H.P Luhn vào năm 1953 về sắp xếp ngoài (external searching).

Danh sách liên kết - một sự tiến bộ thực sự

Cha đẻ thực sự của kỹ thuật bộ nhớ liên kết (linked memory, tiền thân của danh sách liên kết sau này) là ba khoa học gia máy tính nổi tiếng A.Newell5, J.C Shaw5, và H.A Simon5 với các nghiên cứu giải các bài toán heuristic bằng máy tính: đó là chương trình hỗ trợ cho việc chứng minh các công thức logic toán học bằng cacsh thiết kế một ngôn ngữ xử lý danh sách, tiền than của các ngôn ngữ xử lý thông tin ngày nay (list-processing) đầu tiên (gọi là IPL-II, IPL là viết tắc của Information Processing Language) vào mùa xuân năm 1956. Đó là một hệ thống sử dụng các con trỏ (pointers) và các khái niệm quan trọng như danh sách (thời gian này, khái niệm ngăn xếp (stack) chưa xuất hiện). IPL-III, thiết kế 1 năm sau đó, khái niệm “đẩy vào” (push down) và “lấy ra” (pop up) mới được thiết kế kèm coi là các phép toán cơ bản quan trọng. Công trình của Newell, Shaw và Simon đã khích lệ được nhiều người trong việc sử dụng bộ nhớ liên kết, sau đó kỹ thuật này được coi như một khái niệm lập trình cơ bản (ngày này danh sách liên kết là một trong những kiểu cấu trúc thú vị nhất, xuất hiện trong hầu hết các sách và ngôn ngữ lập trình), bài toán thực tế đầu tiên được áp dụng do J.W.Carr giải vào năm 1959. Carr đã chỉ ra rằng danh sách liên kết có thể được xây dựng và xử lý trong các ngôn ngữ lập trình sẵn có mà không cần các thủ tục phức tạp hoặc các hệ thống biên dịch khác. Đầu tiên, bảng liên kết sử dụng các nút 1 vị trí (one-word node), nhưng khoảng năm 1959 người ta phát hiện ra sự hữu dụng của các nút trên nhiều vị trí liên tiếp cũng như danh sách “đa-liên kết” (multilinked) cũng ra đời sau đó. D.T Ross là người đầu tiên thực thi ý tưởng này vào năm 1961, tại thời điểm đó ông ta dùng khái niệm “plex” thay cho khái niệm “node” được sử dụng rộng rãi sau này, “plex” còn được sử dụng như là một tập các nút gắn với một thuật toán duyệt (traversal) tương ứng.

Thông thường, ký hiệu chỉ các trường thông tin trong một nút của danh sách thường bao gồm hai loại: trước (precede) hoặc sau (follow) tên của con trỏ. Vì thế một số viết INFO(P) trong khi một số lại viết P.INFO, hai kiểu ký hiệu này được coi là tương đương. Ký hiệu này rất thuận lợi khi dịch trực tiếp qua các ngôn ngữ như FORTRAN5, COBOL hay tương tự khi đúng ta định nghĩa INFO và mảng LINK và sử dụng con trỏ P làm chỉ số (index). Hơn nữa nó mang tính tự nhiên và gần gũi với các ký hiệu toán học thông thường khi mô tả thuộc tính của các nút trong danh sách. Điều này gây ảnh hưởng đến các nhà thiết kế chương trình dịch, và nó là một phần thú vị của lịch sử ra đời các ngôn ngữ lập trình cũng như cấu trúc dữ liệu cho chúng. Trong cuốn sách kinh điển của mình, GS Knuth có bàn luận kỹ hơn về chúng.

Ngăn xếp và hàng đợi (Stack and Queue)

Có lẽ những người đầu tiên nhận ra nguyên tắc hoạt động của ngăn xếp (vào-sau-ra-trước, LIFO last-in-first-out) và hàng đợi(vào-trước-ra-trước, FIFO first-in-first-out) là các nhân viên kế toán (accountants) khi thực hiện việc giảm thiểu các đánh giá thuế thu nhập; phương pháp “LIFO” và “FIFO” trong đầu tư giá cả được viết trong chương 7, cuốn giáo trình (textbook) về kế toán nổi tiếng của Schlatter vào năm 1957. Khoảng giữ thập kỷ 40, Alan Turing phát triển một cơ chế “stack” tên là Bộ nhớ hồi quy(Reversion Storage) cho sự liên kết các chương trình con (subroutine linkage) với các biến cục bộ (local variables) và các tham số (parameters). Việc sử dụng cấu trúc dữ liệu kiểu “stack” lưu trữ trong các vị trí bộ nhớ tuần tự đã trở nên phổ biến với các ngôn ngữ lập trình ngay từ những ngày đầu do nó là một khía niệm có tính trực giác cao. Lập trình với “stack” dưới dạng danh sách liên kết xuất hiện đầu tiên ở ngôn ngữ IPL và đồng thời được độc lập phát triển bởi Dijkstra vào năm 1960.

Danh sách liên kết vòng (circular) và đôi (doubly)

Nguồn gốc của danh sách liên kết vòng và danh sách liên kết đôi còn khá mờ mịt, không rõ ràng, với nhiều người nó dường như là một ý tưởng rất tự nhiên. Một trong những nhân tố quan trọng khiến hai loại danh sách này phát triển rộng rãi chính là sự tồn tại của hệ thống xử lý danh sách (list-processing system) nói chung. Ivan Suntherland là người ứng dụng việc sử dụng các danh sách liên kết đôi độc lập trong các nút lớn hơn, được giới thiệu trong hệ thống Sketchpad của chính Suntherland (trong luận án tiến sĩ tại học viện kỹ thuật MIT vào năm 1963).

Cùng thời gian, có rất nhiều phương pháp để xác định và duyệt các mảng đa chiều lưu trữ thông tin được phát triển độc lập trong các chương trình thông minh ngay từ thủa ban đầu của lịch sử máy tính, và từ đó một phần chưa biết của các câu chuyện thần kỳ về lập trình máy tính ra đời. Chủ đề này không nằm trong bài viết, bạn đọc quan tâm có thể liên hệ với tác giả hoặc tìm công trình của Hellerman trên tạp chí ACM nổi tiếng vào năm 1962 (trang 205-207) hay của Gower trên tạp chí Computer cùng năm (trang 280-286).

Cây - cấu trúc dữ liệu toán học trong máy tính

Khái niệm cây (tree) xuất hiện trong toán học trước rất lâu sự ra đời của máy tính nói chung và lập trình nói riêng. Khái niệm cây chính thức được định nghĩa một cách toán học lần đầu tiên bởi G.Kirchhoff bằng tiếng Đức vào năm 1847, bản dịch tiếng Anh đăng vào năm 1958. Ông đã dùng khái niệm cây để tìm tập hợp các chu trình cơ bản trong mạng điện dưới định luật mang tên ông (bạn đọc có thể tìm thấy trong giáo trình Vậy lý phổ thông). Cái tên tiếng Anh “tree” bắt đầu xuất hiện khoảng 10 năm sau đó trong một loạt công trình nghiên cứu về cây thừa hưởng từ khái niệm của Kirchhoff và các kết quả đi kèm (về số lượng cây vô hướng, có hướng, thứ tự, …) bởi Arthur Cayley vào những năm từ 1857 đến 1889.

Cấu trúc dữ liệu kiểu cây được biểu diễn một cách chính xác trong bộ nhớ máy tính bắt nguồn từ viế ửu dụng cho các chương trình xử lý công thức đại số (algebraic). Ngôn ngữ máy trong một số máy tính đầu tiên sử dụng mã 3 địa chỉ (three-addess code) để biểu diễn sự tính toán các biểu thức số học, sau đó tương đương với các trường INFO (chỉ thông tin nút của cây), RLINK(Right Link, chỉ nhánh bên phải) và LLINK(Left Link, chỉ nhánh bên trái) thường dùng trong các chương trình máy tính hiện đại có sử dụng cấu trúc dữ liệu kiểu cây. Năm 1952, H.G.Kahrimanian phát riển thuật toán tính sai số các công thức đại số biểu diễn dưới dạng mã 3 địa chỉ mở rộng, công trình sau đó được đăng trong hội nghị về tính toán tự động ở thủ đô Washington DC ở Hoa Kỳ vào năm 1954.

Sau đó, cấu trúc dữ liệu kiểu cây theo nhiều cách được nghiên cứu và phát triển độc lập bởi nhiều nhà khoa học trong nhiều chương trình và ứng dụng máy tính, nhưng các kỹ thuật cơ bản xử lý cấu trúc cây (không phải là xử lý danh sách chung chung) thường ít xuất hiện ngoại trừ một số bản đặc tả chi tiết của một số thuật toán cụ thể. Bản nghiên cứu tóm tắt và tổng kết (survey) đầu tiên với việc nghiên cứu cụ thể các cấu trúc dữ liệu do Iverson và Johnson công bố trên Báo cáo nghiên cứu của tập đoàn máy tính IBM.

Tóm lại, bài viết tóm lược lịch sử phát triển của cấu trúc dữ liệu dùng trong lập trình máy tính nói chung và hai kiểu cấu trúc dữ liệu điển hình (cây và danh sách) nói riêng. Hy vọng nó có ích phần nào và gây thú vị cho bạn đọc.

Tài liệu tham khảo

1. Từ điển bách khoa mở Wikipedia : en.wikipedia.com
2. “Nghệ thuật lập trình máy tính”, tập 1 của GS D. Knuth, ĐH Stanford, Hoa Kỳ

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

[1] Người viết cố gắng chú thích các từ tiếng Anh bên cạnh giúp bạn đọc học thêm được một số thuật ngữ
[2] Danh sách và cây là hai kiểu cấu trúc đầu tiên và cơ bản nhất, mỗi loại có rất nhiều dạng cấu trúc đi kèm.
[3] Hầu hết các loại danh sách sử dụng đều ở dạng tuyến tính.
[4] Nhà khoa học người Đức , một trong những người mở đường cho máy tính và kỹ thuật hiện đại

URL của bài viết này::http://www.vnschool.net/modules.php?name=News&file=article&sid=1439
© Cong ty Cong Nghe Tin hoc Nha truong contact: sales@schoolnet.vn