2016-08-27 11 views
6

Tiêu chuẩn C++ yêu cầu std::partition để có số ứng dụng vị ngữ khác nhau giữa ForwardIteratorBidirectionalIterator. Đối với phiên bản ForwardIterator, số lượng các ứng dụng vị sẽ < = N, nơi N = std::distance(first, last), nhưng đối với các phiên bản BidirectionalIterator, số lượng các ứng dụng vị sẽ < = N/2. Rõ ràng, cả hai phiên bản đều có độ phức tạp thời gian O (N).Tại sao tiêu chuẩn C++ yêu cầu std :: partition để đáp ứng các phức tạp khác nhau cho các loại trình lặp khác nhau?

Câu hỏi của tôi là, tại sao nó lại cung cấp các yêu cầu khác nhau cho các loại trình lặp khác nhau? Yêu cầu đó buộc phải có nhiều trình biên dịch?

ví dụ: MSVC, triển khai chức năng std::partition theo hai cách để đáp ứng yêu cầu đó, có vẻ không quá thanh lịch.

Một câu hỏi nữa: Có bất kỳ thuật toán dựa trên hệ số này, sao cho khi N/2 trở thành N, sự phức tạp tiệm cận sẽ khác nhau? Đối với sự hiểu biết của tôi, nếu chúng tôi xem xét Master's Theorem, đối với biểu mẫu trong T(n) = aT(n/b) + f(n), hệ số trong f(n) không quan trọng nhiều.

C.f. Việc triển khai tương đương phân vùng của MSVC:

template<class BidirIt, class UnaryPred> 
BidirIt partition(BidirIt first, BidirIt last, UnaryPred pred, std::bidirectional_iterator_tag) 
{ 
    while (true) 
    { 
     while ((first != last) && pred(*first)) 
     { 
      ++first; 
     } 
     if (first == last) 
     { 
      break; 
     } 
     --last; 
     while ((first != last) && !pred(*last)) 
     { 
      --last; 
     } 
     if (first == last) 
     { 
      break; 
     } 
     std::iter_swap(first, last); 
     ++first; 
    } 
    return first; 
} 
template<class ForwardIt, class UnaryPred> 
ForwardIt partition(ForwardIt first, ForwardIt last, UnaryPred pred, std::forward_iterator_tag) 
{ 
    first = std::find_if_not(first, last, pred); 
    if (first == last) 
    { 
     return first; 
    } 

    for (ForwardIt src = std::next(first); src != last; ++src) 
    { 
     if (pred(*src)) 
     { 
      std::iter_swap(first, src); 
      ++src; 
     } 
    } 
    return first; 
} 
template<class ForwardIt, class UnaryPred> 
ForwardIt partition(ForwardIt first, ForwardIt last, UnaryPred pred) 
{ 
    return partition(first, last, pred, typename std::iterator_traits<ForwardIt>::iterator_category()); 
} 

Trả lời

4

Vị từ pred được thực thi chính xác N lần trong cả hai trường hợp - mỗi phần tử phải được kiểm tra một lần. Sự khác biệt giữa ForwardIteratorBidirectionalIterator là số tiền hoán đổi.

BidirectionalIterator có tối đa N/2 hoán đổi, vì nó quét phạm vi từ phía trước và từ phía sau cùng một lúc, khi nó đạt giá trị từ bên trái không hoàn thành vị từ và giá trị từ bên phải thực hiện nó, nó hoán đổi chúng. Vì vậy, trong trường hợp xấu nhất nó có thể làm công việc của nó trong N/2 hoán đổi.

ForwardIterator không có lợi thế đó và trong trường hợp xấu nhất có thể thực hiện hoán đổi cho mọi phần tử.

Tiêu chuẩn yêu cầu hai giới hạn khác nhau, vì chúng là cả hai giới hạn tốt nhất có thể nhận được. Vì vậy, các lập trình viên có thể tin tưởng rằng mọi thực thi thư viện chuẩn sẽ hành xử theo cách đó.

+0

Cảm ơn bạn rất nhiều vì đã trả lời câu hỏi của tôi. Như đã chỉ ra, tôi đã phạm sai lầm về số lượng ứng dụng vị ngữ. Cả hai phiên bản đều dành cho các ứng dụng * N *. Đó là số lần hoán đổi khác nhau. Bây giờ tôi có thể hiểu được tiêu chuẩn đòi hỏi vì nó là yêu cầu tối ưu. Cảm ơn một lần nữa cho câu trả lời của các bạn. :) –

+0

Bạn được chào đón. Vui lòng đánh dấu câu trả lời là được chấp nhận nếu câu trả lời cho câu hỏi của bạn. – michalsrb

3

Đúng vậy, độ phức tạp của thời gian vẫn như cũ.

Điều quan trọng cần lưu ý là giao dịch hoán đổi thực sự khá tốn kém. Đặc biệt là đối tượng lớn. Hầu hết thời gian họ liên quan đến ba hoạt động di chuyển. Trong các tình huống như vậy, chúng ta nên giảm số lượng hoán đổi xuống mức tối thiểu. Bằng cách sử dụng các trình vòng lặp hai chiều, chúng tôi nhận được sự cải thiện đáng kể về hiệu quả, mặc dù độ phức tạp của thời gian là như nhau.

Hãy nhớ rằng, trong môi trường thực, nó có thể rất quan trọng cho các hoạt động đơn giản được thực hiện nhanh chóng. Bất cứ khi nào có khả năng làm như vậy, nó phải được thực hiện theo cách này. Khi làm việc với các thuật toán phức tạp (thường là đối với các biến thể của các vấn đề NP-complete) mất nhiều thời gian để tính toán nó có thể rất quan trọng để thực hiện thao tác trong thời gian ít hơn một nửa. Bạn có muốn có độ trễ từ 0,2 giây đến 0,1 giây không? Sự phức tạp thời gian là một loạt các lý thuyết tốt đẹp nhưng thế giới thực không phải là quá đẹp và mỗi phần của thứ hai là quan trọng.

+0

Cảm ơn bạn đã trả lời câu hỏi của tôi. Bây giờ tôi có thể hiểu rằng tiêu chuẩn hy vọng sẽ tối ưu hóa số lượng trao đổi kể từ khi hoạt động hoán đổi có thể tốn kém. Cảm ơn bạn lần nữa vì câu trả lời tốt bụng. :) –

+0

"Đặc biệt là đối với các đối tượng lớn. Hầu hết thời gian chúng liên quan đến ba hoạt động sao chép." ... Tôi hy vọng là không! Đối với một cấu trúc dữ liệu được triển khai đúng, nó sẽ liên quan đến ba hoạt động di chuyển hoặc các hoán đổi con trỏ PIMPL. Khi áp dụng chính xác, hoán đổi không đắt tiền. Rõ ràng vẫn còn tốt hơn để làm một nửa như nhiều hoạt động giá rẻ. –

+0

@NicolasHolthaus yeah, tôi có nghĩa là di chuyển hoạt động của khóa học. Sửa lỗi của tôi. Cảm ơn bạn. – greenshade

Các vấn đề liên quan