std :: bản đồ chèn hoặc std :: bản đồ tìm thấy?


90

Giả sử một bản đồ mà bạn muốn lưu giữ các mục nhập hiện có. 20% thời gian, mục nhập bạn đang chèn là dữ liệu mới. Có lợi thế khi thực hiện std :: map :: find then std :: map :: insert bằng cách sử dụng trình lặp được trả về đó không? Hay nhanh hơn là thử chèn và sau đó hành động dựa trên việc trình vòng lặp cho biết bản ghi đã được chèn hay chưa?


4
Tôi đã được sửa và định sử dụng std :: map :: low_bound thay vì std :: map :: find.
Superpolock

Câu trả lời:


147

Câu trả lời là bạn cũng không. Thay vào đó, bạn muốn làm điều gì đó theo Mục 24 của STL Hiệu quả do Scott Meyers đề xuất :

typedef map<int, int> MapType;    // Your map type may vary, just change the typedef

MapType mymap;
// Add elements to map here
int k = 4;   // assume we're searching for keys equal to 4
int v = 0;   // assume we want the value 0 associated with the key of 4

MapType::iterator lb = mymap.lower_bound(k);

if(lb != mymap.end() && !(mymap.key_comp()(k, lb->first)))
{
    // key already exists
    // update lb->second if you care to
}
else
{
    // the key does not exist in the map
    // add it to the map
    mymap.insert(lb, MapType::value_type(k, v));    // Use lb as a hint to insert,
                                                    // so it can avoid another lookup
}

2
Đây thực sự là cách tìm kiếm hoạt động, mẹo là điều này kết hợp tìm kiếm cần thiết bằng cách tìm và chèn. Tất nhiên, chỉ sử dụng chèn và sau đó xem xét giá trị trả về thứ hai.
puetzk 21/09/08

1
Hai câu hỏi: 1) Việc sử dụng low_bound khác với việc sử dụng tìm kiếm bản đồ như thế nào? 2) Đối với 'bản đồ', có phải trường hợp chọn bên phải của && luôn đúng khi 'lb! = Mymap.end ()' không?
Richard Corden 22-08

11
@Richard: find () trả về end () nếu khóa không tồn tại, Lower_bound trả về vị trí mà mục nên nằm (do đó có thể được sử dụng như gợi ý chèn). @puetzek: Không phải "chỉ chèn" sẽ ghi đè giá trị tham chiếu cho các khóa hiện có? Không chắc liệu OP có mong muốn điều đó hay không.
peterchen

2
bất cứ ai biết nếu có một cái gì đó tương tự cho bản đồ không có thứ tự?
Giovanni Funchal

3
@peterchen map :: insert không ghi đè giá trị hiện có nếu nó tồn tại, hãy xem cplusplus.com/reference/map/map/insert .
Chris Drew

11

Câu trả lời cho câu hỏi này cũng phụ thuộc vào việc tạo ra loại giá trị mà bạn đang lưu trữ trong bản đồ tốn kém như thế nào:

typedef std::map <int, int> MapOfInts;
typedef std::pair <MapOfInts::iterator, bool> IResult;

void foo (MapOfInts & m, int k, int v) {
  IResult ir = m.insert (std::make_pair (k, v));
  if (ir.second) {
    // insertion took place (ie. new entry)
  }
  else if ( replaceEntry ( ir.first->first ) ) {
    ir.second->second = v;
  }
}

Đối với một loại giá trị chẳng hạn như int, ở trên sẽ hiệu quả hơn một tìm kiếm theo sau bởi một chèn (trong trường hợp không có tối ưu hóa trình biên dịch). Như đã nói ở trên, điều này là do việc tìm kiếm thông qua bản đồ chỉ diễn ra một lần.

Tuy nhiên, lệnh gọi chèn yêu cầu bạn phải có "giá trị" mới được xây dựng:

class LargeDataType { /* ... */ };
typedef std::map <int, LargeDataType> MapOfLargeDataType;
typedef std::pair <MapOfLargeDataType::iterator, bool> IResult;

void foo (MapOfLargeDataType & m, int k) {

  // This call is more expensive than a find through the map:
  LargeDataType const & v = VeryExpensiveCall ( /* ... */ );

  IResult ir = m.insert (std::make_pair (k, v));
  if (ir.second) {
    // insertion took place (ie. new entry)
  }
  else if ( replaceEntry ( ir.first->first ) ) {
    ir.second->second = v;
  }
}

Để gọi 'chèn', chúng tôi đang trả tiền cho cuộc gọi đắt tiền để xây dựng loại giá trị của chúng tôi - và từ những gì bạn đã nói trong câu hỏi, bạn sẽ không sử dụng giá trị mới này 20% thời gian. Trong trường hợp trên, nếu thay đổi kiểu giá trị bản đồ không phải là một tùy chọn thì sẽ hiệu quả hơn trước tiên thực hiện 'tìm kiếm' để kiểm tra xem chúng ta có cần xây dựng phần tử hay không.

Ngoài ra, loại giá trị của bản đồ có thể được thay đổi để lưu trữ các chốt cho dữ liệu bằng cách sử dụng loại con trỏ thông minh yêu thích của bạn. Lệnh gọi chèn sử dụng một con trỏ null (cấu tạo rất rẻ) và chỉ khi cần thiết thì kiểu dữ liệu mới được xây dựng.


8

Sẽ hầu như không có bất kỳ sự khác biệt nào về tốc độ giữa 2, find sẽ trả về một trình lặp, insert cũng tương tự và sẽ tìm kiếm trên bản đồ để xác định xem mục đã tồn tại hay chưa.

Vì vậy, nó phụ thuộc vào sở thích cá nhân. Tôi luôn thử chèn và sau đó cập nhật nếu cần thiết, nhưng một số người không thích xử lý cặp được trả về.


5

Tôi sẽ nghĩ nếu bạn thực hiện tìm kiếm rồi chèn, thì chi phí bổ sung sẽ là khi bạn không tìm thấy chìa khóa và thực hiện việc chèn sau đó. Nó giống như xem qua các cuốn sách theo thứ tự bảng chữ cái và không tìm thấy cuốn sách, sau đó xem lại các cuốn sách để xem nơi cần chèn nó. Nó tóm tắt về cách bạn sẽ xử lý các phím và nếu chúng liên tục thay đổi. Bây giờ có một số linh hoạt trong đó nếu bạn không tìm thấy nó, bạn có thể đăng nhập, ngoại lệ, làm bất cứ điều gì bạn muốn ...


3

Tôi bị mất câu trả lời hàng đầu.

Tìm trả về map.end () nếu nó không tìm thấy gì có nghĩa là nếu bạn đang thêm những thứ mới thì

iter = map.find();
if (iter == map.end()) {
  map.insert(..) or map[key] = value
} else {
  // do nothing. You said you did not want to effect existing stuff.
}

chậm gấp đôi

map.insert

cho bất kỳ phần tử nào chưa có trong bản đồ vì nó sẽ phải tìm kiếm hai lần. Một lần để xem nó có ở đó hay không, một lần nữa để tìm vị trí để đặt thứ mới.


1
Một phiên bản của chèn STL trả về một cặp chứa một trình vòng lặp và một bool. Bool cho biết nếu nó có tìm thấy nó hay không, trình vòng lặp là mục nhập tìm thấy hoặc mục nhập được chèn. Điều này rất khó để đánh bại về hiệu quả; không thể, tôi muốn nói.
Zan Lynx vào

4
Không, câu trả lời đã chọn được sử dụng lower_bound, không phải find. Kết quả là, nếu khóa không được tìm thấy, nó sẽ trả về một trình lặp đến điểm chèn chứ không phải cuối. Kết quả là, nó nhanh hơn.
Steven Sudit

1

Nếu bạn lo lắng về tính hiệu quả, bạn có thể muốn xem hash_map <> .

Thông thường, bản đồ <> được thực hiện dưới dạng cây nhị phân. Tùy thuộc vào nhu cầu của bạn, một hash_map có thể hiệu quả hơn.


Sẽ rất thích. Nhưng không có hash_map trong thư viện chuẩn C ++ và PHB không cho phép mã bên ngoài thư viện đó.
Superpolock

1
std :: tr1 :: unardered_map là bản đồ băm được đề xuất thêm vào tiêu chuẩn tiếp theo và sẽ có sẵn trong hầu hết các triển khai hiện tại của STL.
beldaz

1

Tôi dường như không có đủ điểm để để lại nhận xét, nhưng câu trả lời được đánh dấu có vẻ khá dài đối với tôi - khi bạn cân nhắc rằng chèn trả về trình lặp, tại sao phải tìm kiếm Lower_bound, khi bạn chỉ có thể sử dụng trình lặp được trả về. Thật kỳ lạ.


1
Bởi vì (chắc chắn là trước C ++ 11) bằng cách sử dụng chèn có nghĩa là bạn vẫn phải tạo một std::map::value_typeđối tượng, câu trả lời được chấp nhận thậm chí tránh được điều đó.
KillianDS

-1

Bất kỳ câu trả lời nào về hiệu quả sẽ phụ thuộc vào việc thực hiện chính xác STL của bạn. Cách duy nhất để biết chắc chắn là chuẩn cả hai cách. Tôi đoán rằng sự khác biệt khó có thể là đáng kể, vì vậy hãy quyết định dựa trên phong cách bạn thích.


1
Điều này là không đúng sự thật. STL không giống như hầu hết các thư viện khác ở chỗ nó cung cấp các yêu cầu big-O rõ ràng cho hầu hết các hoạt động của nó. Có sự khác biệt được đảm bảo giữa 2 * O (log n) và 1 * O (log n), bất kể cách triển khai các hàm sử dụng để đạt được hành vi O (log n) đó. Liệu sự khác biệt đó có đáng kể trên nền tảng của bạn hay không là một câu hỏi khác. Nhưng sự khác biệt sẽ luôn ở đó.
srm

@srm xác định các yêu cầu big-O vẫn không cho bạn biết một hoạt động sẽ mất bao lâu về mặt tuyệt đối. Sự khác biệt được đảm bảo mà bạn nói đến không tồn tại.
Mark Ransom

-2

map [key] - hãy phân loại nó ra. Đó là truyền đạt ý định của bạn một cách hiệu quả nhất.

Vâng, đủ công bằng.

Nếu bạn thực hiện một tìm kiếm và sau đó một đoạn chèn, bạn đang thực hiện 2 x O (log N) khi bạn bỏ lỡ vì lần tìm chỉ cho bạn biết nếu bạn cần chèn không phải nơi mà đoạn chèn sẽ đến (low_bound có thể giúp bạn ở đó) . Chỉ cần chèn thẳng và sau đó kiểm tra kết quả là con đường tôi muốn.


Không, nếu mục nhập tồn tại, nó trả về một tham chiếu đến mục nhập hiện có.
Kris Kumler

2
-1 cho câu trả lời này. Như Kris K đã nói, việc sử dụng map [key] = value sẽ ghi đè mục nhập hiện có chứ không phải "giữ nguyên" nó như yêu cầu trong câu hỏi. Bạn không thể kiểm tra cho sự tồn tại sử dụng bản đồ [key], bởi vì nó sẽ trả về một đối tượng mặc định được xây dựng nếu chìa khóa không tồn tại, và tạo ra mà là sự xâm nhập cho phím
netjeff

Vấn đề là kiểm tra xem bản đồ đã được điền chưa và chỉ thêm / ghi đè nếu bản đồ chưa có. Sử dụng bản đồ [key] giả sử giá trị luôn ở đó.
srm
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.