intTypePromotion=1
zunia.vn Tuyển sinh 2024 dành cho Gen-Z zunia.vn zunia.vn
ADSENSE

Cấu trúc dữ liệu và giải thuật - Chương 2

Chia sẻ: Nguyễn Nhi | Ngày: | Loại File: PDF | Số trang:14

112
lượt xem
23
download
 
  Download Vui lòng tải xuống để xem tài liệu đầy đủ

CẤU TRÚC MẢNG (ARRAY) Cấu trúc dữ liệu đầu tiên mà ta nói tới là cấu trúc Mảng , đây là 1 cấu trúc rất quen thuộc, nó có mặt ở hầu hết các ngôn ngữ lập trình. I. ĐỊNH NGHĨA Mảng là một tập hợp có thứ tự, bao gồm 1 số xác định n phần tử (n được gọi là độ dài hay kích thước của mảng). Ngoài giá trị, mỗi phần tử của mảng còn dược đặt trưng bởi chỉ số (index), thể hiện thứ tự của phần tử đó tron mảng. Các giá trị của phần...

Chủ đề:
Lưu

Nội dung Text: Cấu trúc dữ liệu và giải thuật - Chương 2

  1. Chương II. CẤU TRÚC MẢNG (ARRAY) Cấu trúc dữ liệu đầu tiên mà ta nói tới là cấu trúc Mảng , đây là 1 cấu trúc rất quen thuộc, nó có mặt ở hầu h ết các ngôn ngữ lập trình. I. ĐỊNH NGHĨA Mảng là một tập hợp có thứ tự, bao gồm 1 số xác định n phần tử (n đ ược gọ i là độ dài hay kích th ước củ a mảng). Ngoài giá trị, mỗi phần tử của mảng còn dược đặt trưng b ởi chỉ số (index), th ể h iện thứ tự của phần tử đó tron mảng. Các giá trị của ph ần tử mảng đ ều cùng một loại. Đối với mảng thường gặp các phép toán : - Tạo lập một mảng . - Duyệt qua các phần tử của mảng. - Tìm kiếm một phần tử của mảng. - Sắp xếp các ph ần tử trong mảng theo một thứ tự nhất đ ịnh. Vì số phần tử của mảng là cố đ ịnh, nên không có phép bổ sung ph ần tử mới vào mảng hoặc loại bỏ mộ t ph ần tử ra khỏi mảng. Vectơ là mảng một chiều, mỗi ph ần tử của nó ứng với một chỉ số. Chẳng hạn: phần tử của vectơ A, kí hiệu là Ai h oặc A[i] với i là chỉ số . Ma trận là mảng hai chiều, mỗ i ph ần tử của nó ứng với 2 chỉ số, ví dụ : ph ần tử của ma trận B, kí hiệu là Bij ho ặc B[i, j] với i gọ i là chỉ số hàng, j gọ i là chỉ số cột. Tương tự người ta cũ ng mở rộng : mảng 3 chiều ,mảng 4 chiều,…. Mảng n chiều. II. CẤU TRÚC LƯU TRỮ CỦA MẢNG 1. Khái quát về cách lưu trữ Mộ t cách đơn giản, có thể hình dung bộ nh ớ của MTĐT là một dãy các ph ần tử cơ sở được đ ánh số kế tiếp nhau (kể từ số 0). Số thứ tự đó được gọi là đ ịa chỉ , một ph ần tử nhớ cơ sở, có địa chỉ đượ gọ i là từ máy. Một ph ần tử dữ liệu có thể được lưu trữ trong máy bởi một ô nh ớ bao gồm mộ t ho ặc nhiều từ máy. Việc truy cập vào ô nhớ dó sẽ được xác định bởi địa ch ỉ của từ máy đ ầu tiên tạo nên ô nhớ đó. Th ường có 2 cách để xác định đ ược địa chỉ. Cách th ứ hất là d ựa vào những đặt tả của việc lư u trữ dữ liệu đ ể tính trực tiếp ra địa chỉ. Địa chỉ n ày được gọi là địa chỉ đ ược tính (computer address). Cách này thương hay được sử dụng trong chương trình d ịch củ a các ngôn ngữ lập trình để tính địa chỉ các phần tử của mảng, tính địa ch ỉ các lệnh th ực hiện tiếp theo v.v… Cách thứ h ai là lưu trữ các địa ch ỉ cần thiết ở một chỗ qui đ ịnh, khi cần xác định sẽ lấy từ đó ra. Lo ại địa ch ỉ này được gọi là (pointer) họăc mối nối (link). Địa
  2. chỉ quay lui của chương trình con đ ể quay trở về chỗ có lời gọ i trong chương trình chính, khi kết thúc việc thự c hiện chương trình con đó, chính là loại đ ịa chỉ này. Cũng có mộ t số cấu trúc lưu trữ sử dụng phố i hợp cả hai cách xác đ ịnh địa chỉ nói trên. 2. Lưu trữ kế tiếp đối với mảng Thông thường mảng được lưu trữ trong máy dưới dạng mộ t vectơ, mà người ta gọ i là vectơ lưu trữ. Đó là một dãy các từ máy kế tiếp nhau (vì vậy người ta gọ i là lưu trữ kế tiếp - sequential storage allocation). Giả sử, ta xét việc lưu trữ kế tiếp đối với mảng 1 chiều, hay 1 vectơ A, mà các phần tử của nó là A[i] với 1≤i≤n. Nếu mỗi ph ần tử của vectơ đuợc lưu trữ trong một ô nh ớ gồm có một từ máy thì đ ể lưu trữ vectơ A, phải dành ra trong bộ nhớ n từ máy kế tiếp nhau, đó chính là n phần tử củ a vectơ lưu trữ V : Phần tử V [i] của vectơ đang xét. Nếu mỗ i ph ần tử của vectơ lưu trũ V (mỗi ô nh ớ của V) phải gồm ω từ máy mới chứa được một phần tử A[i] thì lúc đó V phải bao gồm n ∗ ω từ máy kế tiếp. Địa chỉ mỗi ô nh ớ, nghĩa là mỗi phần tử nhớ V[i] , bây giờ là đ ịa chỉ của từ máy đầu tiên củ a ô nhớ đó. Ví d ụ: Nếu ω = 3 mà đ ịa chỉ của V [1] là 1000 thì địa ch ỉ của V[2] là 1003, của V[3] là 1006. V[1] V[2] V[3] V[n] V A[1] A[2] A[3] ……… A[n] ω Lo Hình 2.1 Địa chỉ củ a V[1] được gọi là địa chỉ g ốc (base address), ký hiệu là Lo. Như vậy việc xác đ ịnh đ ịa chỉ của V[i], hay nói một cách khác: Việc xác định địa ch ỉ của A[i] sẽ được tính ra theo công thứ c: LOC (A[i] ) = Lo + ω ∗ ( i-1) Trong ngôn ngữ C , cận dưới củ a chỉ số không nh ất thiết phải là 1 mà có thể là mộ t số n guyên b nào đó. Khi ấy địa ch ỉ của A[i] được tính b ởi: LOC (A[i] ) = Lo + ω ∗ ( i-b) Đối với mảng 2 chiều hay ma trận, việc lưu trữ các phần tử cũng được thực hiện bởi mộ t vectơ lưu trữ như trên. Gọi B là một ma trận có m hàng, n cột, B sẽ được lưu trữ trong bộ nh ớ bởi vectơ lưu trữ V b ao gồm m∗n∗ω từ máy ( m ỗi phần tử củ a V gồm ω từ máy) Với ngôn ngữ PASCAL, nếu giả sử B có 3 hàng, 4 cộ t (m=3, n=4) thì các phần tử của nó sẽ được lưu trữ như hình sau:
  3. V B11 B12 B13 B14 B21 B22 B23 B24 B31 B32 B33 B34 V[1] V[5] V[9] V[12] Phần tử ở hàng 1 Ph ần tử ở hàng 2 Phần tử ở h àng 3 Hình 2.2 Như vậy nghĩa là : các phần tử của ma trận B sẽ được lưu trữ theo hàng, hết hàng này đến hàng khác. Cách lưu trữ n ày được gọi là : lưu trữ theo thứ tự ưu tiên hàng (row- major order) Cũng có một cách khác, đó là :lưu trữ theo thứ tự ưu tiên cộ t (column major order). Các phần tử của ma trận sẽ được lưu trữ teo cộ t, h ết cột này đ ến cộ t khác. Với ma trận B, 3 hàng, 4 cộ t như trên thì các phần tử sẽ được lưu trữ bởi vectơ lưu trữ V theo như hình 2.3 : V B11 B21 B31 B12 B22 B32 B13 B23 B33 B14 B24 B34 V[1] V[4] V[10] V[7] V[12] Phần tử ở cột 4 Phần tử ở cột 3 Phần tử ở cột 1 Phần tử ở cột 2 Hình 2.2 Việc xây dựng các công thức tính địa ch ỉ cũng được tiến hành tương tự. Nếu ma trận B có m hàng, n cột và mỗi phần tử của vectơ lưu trữ V gồm ω từ máy, thì đ ịa chỉ củ a B[i,j] với 1 ≤i,j≤n : Theo thứ tự ưu tiên hàng sẽ đượctính bởi: LOC(B[i,j] = Lo + [( i-10) ∗ n + (j – 1)] ∗ ω Theo thứ tự ưu tiên cột sẽ được tính bởi : LOC(B[i,j] = Lo + [( i-10) ∗ m + (j – 1)] ∗ ω Trường hợp b1≤i≤ u1,b2≤j≤u 2 thì mỗi hàng sẽ có ( u2-b2+1) phần tử . Khi đó công thứ c tính địa ch ỉ chẳng hạn : theo thứ tự ưu tiên hàng, sẽ là : LOC(B[i,j] = Lo + [( i-10) ∗ (u 2-b 2+1) + j – b2)] ∗ ω Người ta cũng mở rộng cách lưu trữ tương tự đối với mảng nhiều chiều. Chú ý :1) Khi mảng được lưu trữ kế tiếp thì việc truy cập vào mộ t ph ần tử của mảng được thực hiện một cách trực tiếp (truy cập trực tiếp – direct accecss) thông qua “địa chỉ đ ược tính”, nên tốc độ truy cập nhanh và đồng đều đối với mọ i ph ần tử.
  4. 2) Đối với người sử dụng (user) khi lập trình theo một ngôn ngữ nào đó nếu dùng tới cấu trúc mảng (đối với các cấu trúc tiền định khác của ngôn ngữ cũng vậy) thì họ chỉ phải khai báo mảng (theo quy đ ịnh củ a ngôn ngữ đó) và làm việc với các tên mảng và biến chỉ số thôi. Những vấn đề liên quan tới cấu trúc lưu trữ của mảng cũng như việc xác định đ ịa chỉ để truy cập tới các phần tử mảng mà ta đề cập ở trên đều do chương trình dịch đã đảm nhiệm hộ; người sử dụng không phải quan tâm có khi không hề b iết tới nữa. Tuy nhiên cần ph ải hiểu rằng : môi trường làm việc của người sử dụng chỉ là các tên : tên m ảng, tên biến, tên h ằng . . . nhưng thực chất đó là môi trường địa chỉ do chương trình dịch đ iều hành. III. ÁP DỤNG 1. Sắp xếp trên cấu trúc mảng a. Đặt bài toán Sắp xếp là 1 quá trình bố trí lại các phần tử của 1 tập đố i tượng nào đó theo 1 thứ tự ấn định. Bài toán sắp xếp xuất hiện ở rất nhiều ứng dụ ng, dưới nhiều dạng khác nhau : Ở đây, ta chỉ thu h ẹp lại và chỉ phát biểu d ưới dạng đ ơn giản như sau : Cho 1 dãy số A gồm n số khác nhau mà ta coi như 1 vectơ với n phần tử : A[1];A[2];. . .;A[n] Trong đó : A[i] ≠ A[j] nếu i ≠ j; với 1≤i,j≤n Hãy sắp xếp lại các ph ần tử của A để chuyển nó thành 1 dãy số có thứ tự tăng dần (thứ tự giảm d ần cũng tương tự) Có khá nhiều phương pháp khác nhau. Trước hết ta hãy xét tới 1 số phương pháp sắp xếp cơ b ản. b. Ba phương pháp sắ p xếp cơ bản * Sắp xếp kiểu lựa chọn (Selection-Sort) Phương pháp này ta đã xét tới ở chương 1 trong mục 1.3 .Nó dựa vào phép lựa chọn : “chọn số nhỏ nh ất trong dãy chưa được sắp và đổ i chỗ với số đang chiếm vị trí “đầu tiên” của dãy này”. Lúc đầu, dãy ch ưa được sắp chính là dãy A g ồm n số. Sau mỗi phép đổi chỗ, dãy con chưa được sắp sẽ bớt đi 1 phần tử ( dĩ nhiên là dãy con đã được sắp sẽ tăng lên 1 phần tử ). Ta chỉ nhắc lại bằng 1 ví dụ Giả sử A b ao gồm các ph ần tử : 32,51,27,83,66,11,45,75. Quá trình sắp xếp được minh hoạ qua bảng sau : Lượt Số nhỏ nhất A[1] A[2] A[3] A[4] A[5] A[6] A[7] A[8] 32 51 27 83 66 11 45 75
  5. 1 11 11 51 27 83 66 32 45 75 2 27 11 27 51 83 66 32 45 75 3 32 11 27 32 83 66 51 45 75 4 45 11 27 32 45 66 51 83 75 5 51 11 27 32 45 51 66 83 75 6 66 11 27 32 45 51 66 83 75 7 75 11 27 32 45 51 66 75 83 * Sắp xếp kiểu thêm dần (hoặc kiểu chèn – Insertion sort) Sắp xếp kiểu thêm dần được thực hiện theo cách tương tự như cách xếp bài trên tay người chơi bài. Khi đ ã có (k-1) quân bài được sắp xếp theo “đúng thứ tự đang nằm trên tay” n ếu người chơi lấy thêm quân bài thứ k thì họ sẽ ph ải so sánh lần lượt với 1 số quân bài trên tay đ ể tìm chỗ chèn quân mới vào và sau đó trên tay h ọ đ ã có k quân bài đ ã được sắp xếp. Với dãy số A thì thoạt đầu A[1] coi như 1 dãy con đã được sắp xếp, A[2] sẽ được xét tới n ếu : A[2] < A[1], nó sẽ được chèn vào trước A[1], còn A[2] > A[1], nó sẽ được giữ nguyên tại chỗ (coi như được chèn vào sau A[1]). Dãy số A[1],A[2] bây giờ coi như đã được sắp xếp và A[3] được xét tới. Tu ỳ theo kết quả củ a việc so sánh A[3] với A[2],A[1] nó lại được chèn vào sau A[2] hay giữa A[1] và A[2] hay trước A[1]. Quá trình cứ tiếp tục với A[4],A[5] . . . cho tới khi toàn bộ dãy A đuợc sắp xếp. Lượt Số nhỏ nhất A[1] A[2] A[3] A[4] A[5] A[6] A[7] A[8] 32 51 27 83 66 11 45 75 1 11 32 51 27 83 66 11 45 75 2 27 27 32 51 83 66 11 45 75 3 32 27 32 51 83 66 11 45 75 4 45 27 32 51 66 83 11 45 75
  6. 5 51 11 27 32 51 66 83 45 75 6 66 11 27 32 45 51 66 83 75 7 75 11 27 32 45 51 66 75 83 Sau đây là giải thu ật sắp xếp kiểu thêm dần : Void Insertion_Sort(int A[],int N) { int X,i,j; /*dùng X làm ô nhớ phụ để chứa khóa mới đang được xét */ for(i = 1; i < N ; i ++) { X = A[i] ; j=i-1; /*Xác định chỗ cho số mới đang được xét và dịch chuyển các số cần thiết*/ while(X < A[i] && j>=0) { A[j+1]=A[j]) j--; } /*Đưa X vào đúng chỗ củ a nó*/ A[j+1]=X; } } * Sắp xếp kiểu đổi chỗ (Exchange sort) Để tiện cho việc hình dung ý chủ đạo củ a phương pháp, ta tưởng tượng vectơ A được đặt theo chiều th ẳng đứng. Như vậy nó đ ược duyệt từ “đáy” lên. Dọc đường nếu gặp 2 ph ần tử ngược thứ tự (nghĩa là : A[i+1]
  7. Lượt 1 2 3 4 5 6 7 A[1] 32 11 11 11 11 11 11 11 A[2] 51 32 27 27 27 27 27 27 A[3] 27 51 32 32 32 32 32 32 A[4] 83 27 51 45 45 45 45 45 A[5] 66 83 45 51 51 51 51 51 A[6] 11 66 83 66 66 66 66 66 A[7] 45 45 66 83 75 75 75 75 A[8] 75 75 75 75 83 83 83 83 Sau đây là giải thu ật sắp xếp kiểu thêm dần : Void Buble_Sort(int A[],int N) { int X,i,j; for(i = 1; i < N ; i ++) /*duyệt “từ đáy lên”*/ for(j = n-1; j >= i ; j --) { /*Nếu thứ tự ngược thì đổi chỗ*/ If (A[j]
  8. Bây giờ ta xét đến độ phức tạp về thời gian của 3 giải thuật sắp xếp trên. Trước hết ta cần thấy rằng : các phép toán đ áng chú ý khi đánh giá th ời gian của thực hiện sắp xếp là phép so sánh giá trị các phần tử và phép đổi chỗ . Đổi chỗ có khi không thực hiện nhưng so sánh giá trị thì bao giờ cũng phải làm (do đó các phép sắp xếp đã nêu trên thuộc loại sắp xếp dự a vào phép so sánh các giá trị của phần tử). Vì vậy người ta coi phép so sánh các giá trị ph ần tử là phép “tích cự c” và nó làm căn cứ đ ể đ ánh giá thời gian thự c hiện giải thuật. Với SELECTION-SORT ta thấy ở lượt thứ i để tìm số nhở n hất bao giờ cũng cần tới Ci = (n-i) phép so sánh. Số lượng phép so sánh này không hề phụ thuộc gì vào tình trạng ban đầu của dãy A cả. Từ đó suy ra tổng phép so sánh là : n −1 n( n − 1) Cmin=Cmax=Ctb= ∑ (n − i ) = 2 i =1 Do đó Tt(n)=Tx(n)=Ttb(n)=O(n2) Với BUBLE-SORT thì cũng tương tự như vậy Ta cũng có :Tt(n)=Tx(n)=Ttb(n)=O(n2) Riêng với INSERTION-SORT thì hơi khác Số lượng phép so sánh phụ thuộ c vào tình trạng ban đầu củ a A nếu A được sắp xếp rồ i thì khi xét tới A[k], rõ ràng A[k]>A[k-1] (nghĩa là nó cũng lớn hơn mọi số đang đứng trước nó) vì vậy chỉ cần 1 phép so sánh thôi. Do đó tổng số các phép so sánh là (n-1) Cmin=(n-1) và Tt(n)=O(n) Nh ưng nếu dãy A lại có thứ tự n gược với thứ tự sắp xếp thì ở lượt th ứ i cần tới Ci = (i-1) phép so sánh Vậ y : n( n − 1) n Cmax= ∑ (i − 1) = 2 i= 2 Tx(n)=O(n2) Và Người ta cũng chứng minh được : Ttb(n)=O(n2) Tóm lại cả 3 giải thuật trên đều có độ phứ c tạp trung bình về th ời gian là 2 O(n ). Điều đó có nghĩa là tính hiệu qu ả của chúng sẽ giảm sút khi n lớn. c. Sắ p xếp nhanh (Quick-sort) hay sắp xếp kiểu phân hoạch (partition-sort) * Bài toán phân hoạ ch Cho dãy A [1..n] cần phân ho ạch dãy đó thành 2 dãy con sao cho :
  9. 1 phần tử bất kỳ của dãy con bên trái ≤ 1 ph ần tử b ất kỳ củ a dãy - con bên ph ải Giải thuật : 1 . Đặt i=l, i là con trỏ duyệt dãy từ b ên trái sang 2. Đặt j=r, j là con trỏ duyệt dãy từ bên phải sang 3. Lặp lại khi i
  10. /*Kết thúc phân hoạch, dãy trái là a[l..j],dãy phải là a[i..r]*/ If (l
  11. Giải thuật : Tìm tuyến tính là một kỹ thu ật tìm kiếm rất đơn giản và cổ điển. Thuật toán tiến hành so sánh lần lượt Xvới phần tử thứ nh ất, thứ hai,….., của mảng A cho đến khi gặp được ph ần tử có khóa cần tìm, hoặc đã tìm hết mảng mà không thấy X. Các bước tiến hành : Bước 1: i=1; Bước 2 : So sánh a[i] với x, có 2 khả năng : A[i] = X : Tìm th ấy. Dừng. A[i] != X: Sang bước 3. Bước 3 : i=i+1; /*xét phần tử kế*/ Nếu i> N : hết mảng, không tìm thấy. Dừ ng Ngược lại : lặp lại bước 2. Ví dụ : Cho dãy số A : 12, 2, 8, 5, 1, 6, 4, 15 Nếu giá trị cần tìm là 8, giải thu ật được tiến hành như sau : x=8 i = 1: 12 2 8 5 1 6 4 15 x=8 i = 2: 12 2 8 5 1 6 4 15 x=8 i = 3: Dừng. 12 2 8 5 1 6 4 15 Cài đặt : Void SEQUENTIAL(int A[ ],int N,int X) { int i =0; A[N]=X; /*mảng gồm N phần tử từ a[0]..a[N-1]
  12. , thêm phần tử thứ N+1*/ while (A[i]!=X) i++; If(i==N) return -1; /*Chỉ có phần tử thêm vào cuối mảng mới có giá trị X*/ /*tìm thấy tại vị trí i*/ Else return1; } Đánh giá giải thuật : Có th ể ước lượng độ phứ c tạp của giải thuật tìm kiếm qua số lượng các phép so sánh được tiến hành đ ể tìm ra X. Các trường hợp giải thu ật tìm tuyến tính có th ể có : Trường h ợp Số lần so sánh Giải thích Phần tử đầu tiên có Tốt nhất 1 giá trị là X Phần tử cuối cùng có Xấu nh ất N+1 giá trị là X. Giả sử xác xu ất các phần tử trong mảng Trung bình (N+1)/2 nhận giá trị X là như nhau. Vậ y giải thuật tìm tuyến tính có độ phứ c tạp tính toán cấp n : T(n)=O(n). Nhận xét : Giải thuật tìm tuyến tính không phụ thuộc vào thứ tự của các ph ần tử của mảng, do vậy đây là phương pháp tổng quát nh ất để tìm kiếm trên mộ t dãy số bất kỳ. Một thuật toán có thể có nhiều cách cài đặt khác nhau, kỹ thu ật cài đặt ảnh hưởng đ ến tốc độ thực hiện củ a thuật toán. b. Tìm kiếm nhị phân (binary-search) Giả i thuậ t : Đối với nh ững dãy số đã có thứ tự (giả sử thứ tự tăng dần), thì phép tìm kiếm nh ị phân thường được sử dụng. Thay vì so sánh X lần lượt với các giá trị của phần tử A như trong tìm kiếm tu ần tự, người ta so sánh X với phần tử A[k] “ở giữa mảng A” : Nếu X
  13. Nếu X>A[k], tìm kiếm sẽ được thực hiện tiếp với các phần tử thuộc mảng con ở nửa cuố i của A. Nếu X=A[k] thì tìm kiếm đã được thỏa mãn Tìm kiếm sẽ không thỏa mãn n ếu mảng con trở thành rỗng. Ví dụ : Cho dãy số a: 1 2 3 5 6 8 15 20 Nếu giá trị cần tìm là 8, giải thu ật được tiến hành như sau : Left = 1, right = 8, midle = 4 x=8 1 2 3 5 6 8 15 20 Left = 5, right = 8, midle = 6 x=8 1 2 3 5 6 8 15 20 Dừng. Cài đặ t : Void Binary_Search (int A[], int N, int X) { int left = 0; right = N - 1; int midle; do { midle = ( left+right ) / 2; if ( X = A[midle] ) return midle; /*Tìm th ấy x tại vị trí midle*/ else if( X < A[midle] ) right = midle - 1; else left = midle + 1;
  14. } while(left
ADSENSE

CÓ THỂ BẠN MUỐN DOWNLOAD

 

Đồng bộ tài khoản
2=>2