banner

[Rule] Rules  [Home] Main Forum  [Portal] Portal  
[Members] Member Listing  [Statistics] Statistics  [Search] Search  [Reading Room] Reading Room 
[Register] Register  
[Login] Loginhttp  | https  ]
 
Forum Index Thảo luận hệ điều hành Windows Sưu tầm: Các thuật toán sắp xếp thường dùng.  XML
  [Question]   Sưu tầm: Các thuật toán sắp xếp thường dùng. 08/09/2006 13:56:17 (+0700) | #1 | 21694
FaL
Moderator

Joined: 14/04/2006 09:31:18
Messages: 1232
Offline
[Profile] [PM]
Hồi trước Fal có save lại bài "Các kĩ thuật sắp xếp, hay dùng" của:

LQV0604
Group: HVA Member
Posts: 164
Joined: 29-December 03
From: Nothing is impossible
Member No.: 55908

Bây giờ post lên lại đây, tiện thể học tí luôn!
Hãy giữ một trái tim nóng và một cái đầu lạnh
[Up] [Print Copy]
  [Article]   Merge sort 08/09/2006 14:02:10 (+0700) | #2 | 21696
FaL
Moderator

Joined: 14/04/2006 09:31:18
Messages: 1232
Offline
[Profile] [PM]
Merge sort

Nguyên tắc :

VD ta có
12 13 45 32 100 34 65 10

Ta có ở trên là 8 phần tử cần được sắp xếp :
Ý tưởng của merge sort là thay vì sắp xếp 8 phần tử (khó sắp ) thì ta chia đôi dãy đó ra làm đôi (số phần tử nhỏ hơn --> sắp dễ hơn ) và sắp xếp các dãy con rồi ghép 2 dãy con lại ( gọi là merge 2 dãy con )

Vậy ta làm như sau:
Chia đôi --> được hai dãy con mới là 12 13 45 32 và 100 34 65 10
sắp 2 dãy con lại : 12 13 45 32 gọi là dãy A
100 34 65 10 gọi là dãy B
+ Muốn sắp A ta cũng làm y như trên
Chia đôi A , được 2 dãy mới là A11 = { 12 13 } A12 = {45 32 }
Chia đôi B được 2 dãy mới là B11 = {100 34} B12 = {65 10 }
+ Sắp xếp A11, B11 , A12 , B12
+ Muốn sắp xếp A11 thì ta cũng chia đôi đến khi sắp được
ta có 2 dãy con là A21 = {12} A22 = { 13}
Sắp 2 dãy con trên được ( đơn giản vì chỉ có một phần tử ) là A21 = {12 } A22 = {13}
Sắp xong thì ta merge lại thành A11 = { 12 13 }
+ Tương tự sắp xếp cho B11 , A12 , B12 ta cũng có
B11 = {34 100} B12 = {10 65 } A12 = {32 45 }
+Sắp xếp xong , ta sẽ merge lại A11 , A12 thành A = { 12 13 32 45 }
B11 , B12 thành B = { 10 34 65 100 }
Sắp xong A , B , ta sẽ merge chúng lại thành dãy ban đầu :
{10 12 13 32 34 45 65 100 }

Phương pháp merge:
VD A = { 12 13 32 45 }
B = { 10 34 65 100}

Đầu tiên lấy phần tử đầu tiên của A và B : 12 và 10
10 < 12 nên ta lấy 10 bỏ vào mảng kết quả là C = {10}
Giử lại số 12 , và lấy tiếp phần tử thay thế 10 trong mảng B là 34
So sánh 12 và 34 . 12 < 34 , lấy 12 ra và bỏ vào C = {10 12}
Giử lại 34 . Lấy phần tử kế tiếp để thay cho 12 trong mảng A là 32
So sánh 32 và 34 chọn 32 bỏ vảo C = { 10 12 32 }
........ Làm tương tự ......
Đến bước cuối cùng A hết phần tử B còn lại B = { 65 100}
Ta sẽ bỏ toàn bộ mảng B vào C . Kết quả C đã được merge và có thứ tự

Giải thuật:(cho trường hợp dùng list để chứa các phần tử cần sort)

Sortable_List là một lớp list có đặc điểm là có hàm sort
Node là một template class biểu diễn cho các node trong list
Record là class dùng để biểu diễn data cần sắp xếp . ( VD như sắp một dãy các số nguyên , hay VD là sắp theo tên của các record bao gồm tên , tuổi , số điện thoại ...)
sublist là list cần sắp xếp
 

Code:
Sortable_List<Record>::recursive_merge_sort(Node<Record> *&sublist )
{
    if ( sublist  != 0 && sublist->next != 0 } //tức là có tồn tại 2 node trở lên để có thể chia đôi sublist được
    {
              Node <Record > *second_half = divide_from (sublist); 
              recursive_merge_sort(sublist) ;
              recursive_merge_sort(second_half) ;
              sublist = merge(sublist, second_half)
    }
}

Node<Record>* Sortable_List <Record>::divide_from( Node<Record> *sublist)
{
          Node<Record> *position , *midpoint , *second_half ;
          if (( midpoint = sublist) == NULL) return NULL ; // hổng có ds để chia 
          position  = midpoint->next ;
          while(position != 0) 
        {
                  position = position->next ;
                  if ( position  != 0)
                  {
                            midpoint = midpoint->next ;
                            position = position->next ;  
// midpoint duyêt 1 thì position duyệt 2 node cùng 1 lúc nên khi position kết thúc = NULL thì midpoint cũng đang ở vị trí chính giữa;
                  }
        }
          second_half = midpoint->next ;
          midpoint->next = NULL ;
          return second_half ;
}



Node <Record> * merge(Node<Record> *first , Node<Record>* second ) 
{
      Node<Record> *last_sorted ;
      Node<Record> combined ;
      last_sorted = &combined ;
      while ( first != NULL && second !=  NULL )
      {
              if (first->entry <= second->entry ) {
                        last_sortesorted->next = first ;
                        last_sorted = first ; 
                        first = first->next ;
              }
              else 
              {
                      last_sorted->next = second ; 
                      last_sorted = second ;
                      second = second->next ;
              }
        }
        if ( first == NULL ) 
              last_sorted->next = second ;
        else
              last_sorted-> next = first  ;
        return combind.next 
}
//

PS : Hàm merge này viết hay , không tạo ra một ds mới để chứa kết quả như ta trình bày ở VD mà chỉ dùng một ds các con trỏ (combined) để chỉ đến thứ tự các node ở trên cả hai first và second. Cách này có lợi nếu Record là một trường phức tạp .
Hãy giữ một trái tim nóng và một cái đầu lạnh
[Up] [Print Copy]
  [Article]   Quick sort 08/09/2006 14:05:12 (+0700) | #3 | 21697
FaL
Moderator

Joined: 14/04/2006 09:31:18
Messages: 1232
Offline
[Profile] [PM]
Quick sort

Ý tưởng:
Gần giống như merge sort , ta sẽ chia đôi mảng cần xếp rồi sắp xếp các mảng con , sau đó sẽ ghép mảng con đã sắp xếp thành mảng ban đầu nhưng đã sắp xếp

Điểm khác nhau: Là chổ ta chia đôi mảng con theo nguyên tắc riêng :
Gọi điểm mà tại đó ta chia đôi mảng ban đầu có trị là pivot (không nhất thiết là nằm đúng vị trí chính giữa . Nhưng giải thuật sẽ chạy tốt nếu nó nằm gần điểm chính giữa) và ta sẽ không cần merge 2 dãy con (do đó nhìn chung sẽ chạy nhanh hơn ) , thỏa điều kiện là tất cả những phần tử bên trái pivot đều nhỏ hơn pivot và nằm bên phải pivot thì lớn hơn pivot

VD :
2 4 6 7 18 14 13
Ta chọn phần tử pivot = 7 ( do bên trái của nó nhỏ hơn pivot = 7 , bên phải lớn hơn pivot = 7 ) (Thực ra ta phải tìm pivot , do ở đây làm bằng tay nên dễ thấy )
Chia đôi : A = { 2 4 6 } B = { 18 14 13 }
+ Sắp A
+ Trong dãy A ta cũng chọn phần tử pivot là 4
+ Được 2 dãy con là A11 = {2 } A12 = {6}
+ Sắp A11 ( sắp sẵn rồi )
+ Sắp A12 ( sắp sẵn rồi )
+ Tạo lại mảng A = { A11 được sắp , pivot , A12 được sắp } = {2 4 6 }
+Sắp B . Trong B ta không thấy được pivot do chọn cái nào ta cũng không thấy thỏa . Nhưng mục tiêu của quick sort là làm sao ta được dãy
<C= dãy có trị nhỏ hơn pivot > pivot < D= dãy có trị lớn hơn pivot >
Sắp 2 dãy con C , D rồi ghép lại ta được dãy sắp thứ tự .


Vì vậy áp dụng một giải thuật tìm pivot trong B( sẽ trình bày sau ) ta sẽ được C = { 13} D = {18} và pivot là 14 .
+ Sắp dãy con C , D ( do chỉ có một phần tử nên không sắp , hoặc tìm pivot)
+ Ghép lại tạo thành mảng B được sắp B = { 13 14 18 }

+Ghép A và B để tạo thành mảng được sắp xếp ban đầu
 


Thuật toán

Code:
void Sortable_List<Record>::quick_sort()
{
          recursive_quick_sort(0 , count -1 ) ;
}

void Sortable_List<Record>::recursive_quick_sort(int low , int high )
{
          int pivot_position ;
          if (low < high )
          {
                    pivot_position = partition (low , high ) ;
                    recursive_quick_sort(low , high ) ;
                    recursive_quick_sort(pivot_position +1 , high ) ;
          }
}


int Sortable_List<Record>::partition(int low , int high ) 
{
              Record pivot ;
              int i , last_small;
              swap ( low , (low +high) /2 ;
              pivot = entry[low] ;
              last_small = low ;
              for ( i = low +1 ; i <= high ; i ++ )
              {
                        if(entry[i] < pivot )
                        {
                                    last_small = last_small +1 ;
                                    swap ( last_small , i ) ;
                        }
                
              }
                swap (low , last_small );
                return last_small ;
}


PS : Hiệu quả của giải thuật quicksort phụ thuộc rất nhiều vào việc chọn cho được pivot tốt ( tức nếu pivot nằm gần chính giữa thì đạt gần tối ưu )

Có rất nhiều phiên bản quicksort khác nhau nếu ta thay đôi cách tìm pivot . VD Ở cách trên phương pháp làm là chọn phần tử giữa để lấy trị của nó làm pivot ,rồi sau đó sẽ di chuyển phần tử giữa để nó đúng vào vị trí cần có của pivot là <bên trái nhỏ hơn pivot > pivot <bên phải lớn hơn pivot > . Ta có thể chọn cách khác là lấy phần tử đầu làm trị pivot . Sau đó di chuyển phần tử đầu đến đúng vị trí cần có của pivot .
Do đó ta sẽ có rất nhiều cách để hiện thực một ý tưởng và nhiều phiên bản của quick sort . Bởi vậy tui thích xem ý tưởng hơn là đưa ra những đoạn code rồi ngồi xem code nó chạy như thế nào .
Hãy giữ một trái tim nóng và một cái đầu lạnh
[Up] [Print Copy]
  [Article]   Heap Sort 08/09/2006 14:11:27 (+0700) | #4 | 21701
FaL
Moderator

Joined: 14/04/2006 09:31:18
Messages: 1232
Offline
[Profile] [PM]

Heap là một cấu trúc dữ liệu , có thể được biểu diễn thông qua 2 cách :
-Dạng thứ 1: Dạng cây nhị phân có đặc điểm là node cha thì lớn hơn 2 node con trực tiếp của nó .
-Dạng thứ 2: nếu ta đánh số các node theo thứ tự từ trên xuống và từ trái qua . Bắt đầu là node root = 0 , thì ta có thể định nghĩa heap thông qua mảng một chiều , có đặc điểm là phần tử thứ k sẽ lớn hơn các phần tử thứ 2k+1 và 2k+2 . Ta có thể dễ nhận thấy là phàn tử thứ 0 sẽ tương ứng với root trong cây ở cách biểu diễn thứ 1

Nguyên tắc sắp xếp của heap sort
Dựa vào tính chất của heap trong cách biểu diễn thứ 1 và thứ 2 , ta có thể thấy phần tử đầu tiên trong cách biểu diễn theo mảng sẽ là phần tử lớn nhất ---> cách sắp xếp đơn giản là : ( Gọi mảng ban đầu là A )

Khởi tạo : Tạo heap từ mảng ban đầu đã cho (mảng A )
1. Lấy phần tử đầu tiên trong mảng ra bỏ vào mảng kết quả
2. Tạo lại heap từ mảng A
3.Quay lại bước 1

VD : Ta lấy một mảng đã được tạo thành một heap :

y r p d f b k a c

Lấy phần tử đầu tiên là y bỏ vào mảng kết quả C = { y }

khi này A = r p d f b k a c
Tạo heap A = r f p d c b k a

Lấy phần tử đầu tiên ra là r bỏ vào mảng C = { r y }
Khi này A = { f p d c b k a }
Tạo heap cho A = { p f k d c b a}

Lấy phần tử đầu tiên ra là p bỏ vào mảng C = { p r y }
Khi này A = { f k d c b a }
Tạo heap cho A = { k f b d c a}

Lấy phần tử đầu tiên ra là k bỏ vào mảng C = { k p r y }
Khi này A = { f b d c a }
Tạo heap cho A = { f d b a c}

Lấy phần tử đầu tiên ra là f bỏ vào mảng C = { f k p r y }
Khi này A = { b d c a }
Tạo heap cho A = { d c b a}

Lấy phần tử đầu tiên ra là d bỏ vào mảng C = {d f k p r y }
Khi này A = { c b a }
Tạo heap cho A = { c a b }


Lấy phần tử đầu tiên ra là c bỏ vào mảng C = {c d f k p r y }
Khi này A = { b a }
Tạo heap cho A = { b a }

Lấy phần tử đầu tiên ra là b bỏ vào mảng C = {b c d f k p r y }
Khi này A = { a }
Tạo heap cho A = { a }

Kết thúc ta có được mảng C đã có thứ tự .

Cải tiến:
Ta có thể hạn chế việc sử dụng thêm mảng C bằng cách tận dụng luôn mảng A ban đầu . Ta làm như sau

A = y r p d f b k a c
Bước 1 :
Lấy y ra
Lấy c ra
Bỏ y vào chổ của c .
Bỏ c vào chỗ của y

Khi ta bỏ y vào chỗ của c thì giống như ta bỏ y vảo mảng C .
Khi này mảng A sẽ coi như gồm 2 phần A = c r p d f b k a ---- y

Bước 2 : tạo heap cho phần đứng trước của A là c r p d f b k a
Phần sau là chứa y để nguyên

Ta sẽ có A mới là : r f p d c b k a -- y

Quay lại bước 1 : Lấy r , a ra và swap r và a
A sẽ thành A= a f p d c b k -- r y
Tạo heap cho A = p f k d c b a -- r y
...........
Làm tương tự đến khi kết thúc

Qua VD ta thấy rằng phần quan trọng nhất là làm sao sinh ra heap từ một mảng cho trước

Sau đây là phần code cho phần cải tiến
Giải thuật

Post Condition : Dùng để phục hồi lại heap .
Pre Condition :


Ta sẽ có A mới là : r f p d c b k a -- y
Quay lại bước 1 : Lấy r , a ra và swap r và a
A sẽ thành A= a f p d c b k -- r y
Tạo heap cho A = p f k d c b a -- r y

Thì khi này current chính là a
low là 0
high là 7
 


Code:
void Sortable_List<Record>::insert_heap(const Record ¤t, int low , int high ) 
{
              int large ; 
              large = 2*low+1 ;
              while ( large <= high ) {
                        if (large < high  && entry[large] < entry[large+1] )
                                    large ++ ;
                        if(current >= entry[large] ) 
                          {
                                break ;
                          }
                          else 
                          {
                                entry[low] = entry[large] ; 
                                low = large ;
                                large = 2 *  low + 1 ;
                          }
              }
              entry[low] = current ; 
}

Tạo ra heap lúc ban đầu , lúc chưa chạy giải thuật
void Sortable_List<Record>::bulidheap()
{
          int low ;
          for ( low = count / 2- 1; low >= 0 ; low -- )
          {
                  Record current = entry[low] ;
                  insert_heap(current, low, count -1) ;
          }
}

void Sortable_List<Record>::heapsort ()
{
            Record current ;
            int last_unsorted ;
            buildheap() ;
            for ( last_unsorted = count -1 ; last_unsorted > 0 ; last_unsorted -- )
            {
                      current = entry[ last_unsorted] ;
                      entry[last_unsorted]  = entry[0] ;
                      insert_heap(current,0,last_unsorted-1) ;
            }
}

Hãy giữ một trái tim nóng và một cái đầu lạnh
[Up] [Print Copy]
  [Article]   Insertion Sort 08/09/2006 14:15:16 (+0700) | #5 | 21703
FaL
Moderator

Joined: 14/04/2006 09:31:18
Messages: 1232
Offline
[Profile] [PM]
insertion sort

VD :
A = { 5 8 6 3 10 }
Insertion sort làm như sau :

Chia mảng A làm 2 phần sorted và unsorted
Ban đầu sorted là B = { 5 }
Unsorted là C = { 8 6 3 10 }

Lần làm thứ nhất :
Lấy phần tử đầu tiên của C là 8 ra---> C = { 6 3 10 }
Tìm vị trí của số 8 trong mảng B ---> B = { 5 8 }

Lần làm thứ hai :
Lấy phần tử đầu tiên của C là 6 ra---> C = { 3 10 }
Tìm vị trí của số 6 trong mảng B ---> B = { 5 6 8 }

Lần làm thứ ba :
Lấy phần tử đầu tiên của C là 3 ra---> C = { 10 }
Tìm vị trí của số 3 trong mảng B ---> B = { 3 5 6 8 }

Lần làm thứ tư :
Lấy phần tử đầu tiên của C là 10 ra---> C = { }
Tìm vị trí của số 10 trong mảng B ---> B = { 3 5 6 8 10}

Kết thúc thuật toán  


Ý nghĩa của insertion sort là lấy một phần tử của mảng ra và insert vào vị trí thích hợp trong mảng

Giải thuật

Code:
void Sortable_List<Record>::insertion_sort() // phien bản nay list được hien thưc thông qua mảng chứ kg phải con trỏ 
{ 
    int  first_unsorted ;
    int position ;
    Record current ;
    for( first_unsorted = 1 ; first_unsorted < count ; first_unsorted ++)
    {
                if(entry[first_unsorted] < entry[first_unsorted-1])
                {
                        position = first_unsorted ;
                        current = entry[first_unsorted] ;
                        do {
                                    entry[position] = entry [ position -1] ;
                                    position -- ;
                        } while (position > 0 && entry[position-1]> current );
                        entry[position] = current ;
                }
      }
}

Hãy giữ một trái tim nóng và một cái đầu lạnh
[Up] [Print Copy]
  [Article]   Selection Sort 08/09/2006 14:17:45 (+0700) | #6 | 21704
FaL
Moderator

Joined: 14/04/2006 09:31:18
Messages: 1232
Offline
[Profile] [PM]
Selection Sort

Nguyên tắc :
Chia mảng cần sắp thành 2 phần
Phần đã được sắp và phần chưa được sắp :
<A = phần được sắp> < B= phần chưa được sắp>
C = phần tử đầu tiên của B

Bước 1 : Tìm trong B phần tử lớn nhất max_key
Bước 2 : swap C và max_key ( hoán đổi vị trí )
Bước 3 : Bỏ max_key vào A . Khi này A mới = { A cũ , max_key } .
Quay lại bước 1  


Gọi Sortable_List là list được hiện thực bằng mảng
Code:
void Sortable_List<Record>::selectionsort() 
        {
                    for ( int position = count -1 ; position > 0 ; position -- ) 
                    {
                              int max = max_key(0,position) ; // tìm key lớn nhất 
                              swap (  max , position ) ; 
                    }
        }
        
        void Sortable_List<Record>::swap ( int low , int high)
        {
                    Record temp ;
                    temp = entry[low] ;
                    entry[low] = entry[high] ;
                    entry[high] = temp ;
        }
        
        .... max_key(int low , int high) 
        {
                int largest , current ;
                largest = low ;
                for ( current = low +1 ; current <= high ; current ++ )
                        if ( entry[largest] < entry[current] )
                        {
                                    largest = current ;
                          }
                  return largest ;
          }

Hãy giữ một trái tim nóng và một cái đầu lạnh
[Up] [Print Copy]
  [Question]   Sưu tầm: Các thuật toán sắp xếp thường dùng. 08/09/2006 14:27:45 (+0700) | #7 | 21709
FaL
Moderator

Joined: 14/04/2006 09:31:18
Messages: 1232
Offline
[Profile] [PM]
Tạm thời bây nhiêu đã. Có chỗ nào sai sót mong anh em góp ý, đặc biệt là bác LQV0604.
Hãy giữ một trái tim nóng và một cái đầu lạnh
[Up] [Print Copy]
  [Article]   bổ xung thuật toán sắp xếp 08/09/2006 17:26:41 (+0700) | #8 | 21734
[Avatar]
learn2hack
Elite Member

[Minus]    0    [Plus]
Joined: 29/06/2006 16:32:37
Messages: 825
Offline
[Profile] [PM] [WWW]
mình xin bổ xung thêm một vài thuật toán sắp xếp khác.

1. thuật toán sắp xếp nổi bọt (buble sort):
trong thuật toán này, các giá trị trong mảng sẽ được duyệt từ cuối lên đầu, tại mỗi bước sẽ so sánh giá trị của 2 phần tử kề nhau. nếu chúng bị ngược thứ tự thì đổi lại vị trí. sau 1 lần như vậy thì phần tử có giá trị nhỏ nhất sẽ được chuyển về đầu mảng. và quá trình tiếp tục duyệt từ cuối đến phần tử thứ 2, rồi từ cuối đến phần tử thứ 3, ...
sở dĩ gọi là nổi bọt vì quá trình so sánh giữa các cặp phần tử giống như "bọt" nổi trên mặt nướcsmilie.

Code:
void bublesort(double *a){
  double temp;
  for (int i = 2; i <= n; i++)
    for (int j = n; j >= i; j--)
      if (a[j] < a[j-1]) {
        temp = a[j];
        a[j] = a[j+1];
        a[j+1] = temp;
      }
}


thuật toán này có độ phức tạp là O(n^2).

2. thuật toán sắp xếp đếm phân phối (distribution counting):
thuật toán này được áp dụng trong trường hợp đặc biệt, khi mà tất cả các giá trị trong mảng đều là số nguyên và thuộc khoảng [0..M] đã biết.
ý tưởng của thuật toán là đếm xem trong khoảng [0..M] đó có bao nhiêu giá trị 0 (giả sử là a), bao nhiêu giá trị 1 (giả sử là b), ..., bao nhiêu giá trị M (giả sử là z). sau đó xếp lại mảng bằng cách đặt a phần tử 0 ở đầu, tiếp theo đặt b phần tử 1 tiếp theo, ..., và đặt z phần tử M ở cuối cùng.
để giảm thiểu thì việc đếm trên không đếm những giá trị không có trong mảng. giả sử mảng a có các giá trị a[1], a[2], ..., a[k] trong tổng số n phần tử thì chỉ đếm số lần lặp lại của k giá trị đó.

Code:
double *distributioncounting(double *a){
  int c[M+1]; /* lưu số lần xuất hiện của các phần tử mảng a */
  int v;
  double b[n];
  for (int i = 0; i <= M; i++) c[i] = 0;
  for (i = 1; i <= n; i++) c[a[i]]++; /* đếm số lần xuất hiện của a[i] trong khoảng [0..M] */
  for (i = 1; i <= M; i++) c[i] += c[i-1]; /*tính vị trí cuối của mỗi đoạn con */
  for (i = n; i > 0; i--) {
    v = a[i];
    b[c[v]] = a[i];
    c[v]--;
  }
  return b;
}


độ phức tạp của thuật toán này là O(max(M, n)), do kết quả của phép đếm. nhược điểm của thuật toán này là khi M quá lớn thì khó thực hiện.
Blog: http://hontap.blogspot.com
Tải phần mềm miễn phí: http://www.taiphanmem.org
[Up] [Print Copy]
[digg] [delicious] [google] [yahoo] [technorati] [reddit] [stumbleupon]
Go to: 
 Users currently in here 
2 Anonymous

Powered by JForum - Extended by HVAOnline
 hvaonline.net  |  hvaforum.net  |  hvazone.net  |  hvanews.net  |  vnhacker.org
1999 - 2013 © v2012|0504|218|