Tạo kết hợp từ một nhóm các cặp mà không lặp lại các phần tử


28

Tôi có một bộ các cặp. Mỗi cặp có dạng (x, y) sao cho x, y thuộc các số nguyên từ phạm vi [0,n).

Vì vậy, nếu n là 4, thì tôi có các cặp sau:

(0,1) (0,2) (0,3)
(1,2) (1,3) 
(2,3) 

Tôi đã có các cặp. Bây giờ, tôi phải xây dựng một tổ hợp bằng n/2các cặp sao cho không có số nguyên nào được lặp lại (nói cách khác, mỗi số nguyên xuất hiện ít nhất một lần trong kết hợp cuối cùng). Sau đây là các ví dụ về kết hợp đúng và không chính xác để hiểu rõ hơn

 1. (0,1)(1,2) [Invalid as 3 does not occur anywhere]
 2. (0,2)(1,3) [Correct]
 3. (1,3)(0,2) [Same as 2]

Ai đó có thể gợi ý cho tôi một cách để tạo ra tất cả các kết hợp có thể, một khi tôi có các cặp.


Có lẽ bằng cách sử dụng một mảng 2d để thể hiện các cặp của bạn. Các kết hợp hợp lệ tương ứng với một lựa chọn của n ô mảng sao cho mỗi hàng và cột chứa chính xác 1 ô được chọn.
Joe

4
Bạn đang nói rằng đầu vào là tập hợp của tất cả các cặp? Nếu vậy, bạn chỉ nên nói đầu vào đơn giản là . n
rgrig

2
luôn luôn? Nếu không, các câu lệnh "không có số nguyên nào được lặp lại" và "mỗi số nguyên xuất hiện ít nhất một lần trong tổ hợp cuối cùng" là trái ngược nhau. n
Dmytro Korduban

1
cùng một vấn đề như @rgrig: là đầu vào tất cả các cặp không có thứ tự hay nó là một tập hợp các cặp có thể tùy ý? Nếu nó là tất cả các cặp thì bạn chỉ có thể nói đầu vào là , không cần đưa ra danh sách. n
Kaveh

1
Bạn quan tâm đến việc tạo ra tất cả các kết quả khớp hoàn hảo của biểu đồ trên điểm được xác định bởi bộ cặp ban đầu của bạn. Hơn nữa, có vẻ như bạn lấy biểu đồ đó làm biểu đồ hoàn chỉnh trên các điểm đó. Câu hỏi của bạn sẽ rõ ràng hơn nếu bạn đề cập đến điều đó. Có ( n - 1 ) ! ! : = 1 × 3 × 5 × × ( n -n matchings như vậy. (n1)!!:=1×3×5××(n1)
Marc van Leeuwen

Câu trả lời:


14

Một cách trực tiếp là một thủ tục đệ quy thực hiện như sau trên mỗi lần gọi. Đầu vào của thủ tục là một danh sách các cặp đã được chọn và một danh sách tất cả các cặp.

  1. Tính số nhỏ nhất chưa có trong danh sách đầu vào. Đối với lần gọi đầu tiên, tất nhiên đây sẽ là 0, vì không có cặp nào được chọn.
  2. Nếu tất cả các số được bảo hiểm, bạn có một kết hợp chính xác, in nó ra và trả lại bước trước đó. Mặt khác, con số nhỏ nhất chưa được khám phá là mục tiêu chúng ta sẽ nhắm tới.
  3. Tìm kiếm thông qua các cặp tìm kiếm một cách để bao gồm số mục tiêu. Nếu không có, thì chỉ cần quay lại mức đệ quy trước đó.
  4. Nếu có một cách để bao gồm số mục tiêu, hãy chọn cách đầu tiên và gọi lại toàn bộ quy trình một lần nữa, với cặp chỉ cần chọn thêm vào danh sách các cặp đã chọn.
  5. Khi trả về, hãy tìm cách tiếp theo để che số mục tiêu bằng một cặp, mà không chồng chéo một cặp đã chọn trước đó. Nếu bạn tìm thấy một, chọn nó và một lần nữa gọi đệ quy tiếp theo.
  6. Tiếp tục bước 4 và 5 cho đến khi không còn cách nào khác để che số mục tiêu. Đi qua toàn bộ danh sách các cặp. Khi không có lựa chọn chính xác hơn, quay trở lại mức đệ quy trước đó.

Cách để hình dung thuật toán này là với một cây có các đường dẫn là chuỗi các cặp không chồng chéo. Cấp độ đầu tiên của cây chứa tất cả các cặp chứa 0. Đối với ví dụ trên, cây là

           Nguồn gốc
             |
     ----------------
     | | |
   (0,1) (0,2) (0,3)
     | | |
   (2,3) (1,3) (1,2)

Trong ví dụ này, tất cả các đường dẫn qua cây thực sự cho các bộ sưu tập chính xác, nhưng ví dụ nếu chúng ta bỏ qua cặp (1,2) thì đường dẫn ngoài cùng bên phải sẽ chỉ có một nút và sẽ tương ứng với tìm kiếm ở bước 3 không thành công.

Các thuật toán tìm kiếm thuộc loại này có thể được phát triển cho nhiều vấn đề tương tự như liệt kê tất cả các đối tượng của một loại cụ thể.


Có ý kiến ​​cho rằng có lẽ OP có nghĩa là tất cả các cặp đều ở đầu vào, không chỉ là một bộ trong số chúng như câu hỏi nói. Trong trường hợp đó, thuật toán dễ dàng hơn nhiều vì không còn cần thiết phải kiểm tra cặp nào được phép. Thậm chí không cần thiết phải tạo tập hợp tất cả các cặp; mã giả sau đây sẽ làm những gì OP yêu cầu. Ở đây là số đầu vào, "danh sách" bắt đầu là một danh sách trống và "được bảo hiểm" là một mảng có độ dài n được khởi tạo thành 0. Nó có thể được thực hiện hiệu quả hơn một chút nhưng đó không phải là mục tiêu trước mắt của tôi.nn

sub cover {
  i = 0;
  while ( (i < n) && (covered[i] == 1 )) {
   i++;
  }
  if ( i == n ) { print list; return;}
  covered[i] = 1;
  for ( j = 0; j < n; j++ ) {
    if ( covered[j] == 0 ) {
      covered[j] = 1;
      push list, [i,j];
      cover();
      pop list;
      covered[j] = 0;
    }
  }
  covered[i] = 0;
}

Điều này sẽ làm việc, nhưng nó có thể không phải là cách hiệu quả nhất để làm điều đó.
Joe

2
Cuối cùng, vấn đề là bằng cách nào đó liệt kê các đường dẫn của cây đó. Nếu số lượng cặp trong danh sách đầu vào nhỏ hơn nhiều so với số lượng cặp có thể, loại thuật toán này sẽ hoàn toàn hiệu quả, đặc biệt nếu một số bảng băm được sử dụng để giúp ghi nhớ những con số nào đã được đề cập ở mỗi bước, do đó điều này có thể được truy vấn trong thời gian liên tục.
Carl Mummert

Nếu danh sách sử dụng con trỏ, thì Liên kết Nhảy múa của Knuth đáng để xem. Khi bạn trở lại hình thức một cuộc gọi đệ quy và phải khôi phục trạng thái trước đó của danh sách.
uli

10

Bạn có thể giải quyết nó lặp đi lặp lại. Giả sử bạn có tất cả các giải pháp cho phạm vi [ 0 , n ) . Sau đó, bạn có thể dễ dàng xây dựng các giải pháp S n + 2 từ S n . Kích thước tăng lên cực kỳ nhanh chóng với n , vì vậy có thể tốt hơn khi viết một trình tạo thay vì giữ tất cả các bộ trong bộ nhớ, xem ví dụ Python bên dưới.Sn[0,n)Sn+2Snn

def pairs(n):
    if (n%2==1 or n<2):
        print("no solution")
        return
    if (n==2):
        yield(  [[0,1]]  )
    else:
        Sn_2 = pairs(n-2) 
        for s in Sn_2:
            yield( s + [[n-2,n-1]] )
            for i in range(n/2-1):
                sn = list(s)
                sn.remove(s[i])
                yield( sn + [ [s[i][0], n-2] , [s[i][1], n-1] ] )
                yield( sn + [ [s[i][1], n-2] , [s[i][0], n-1] ] )

Bạn có thể liệt kê tất cả các cặp bằng cách gọi

for x in pairs(6):
   print(x)

6

Cập nhật : câu trả lời trước đó của tôi xử lý các biểu đồ lưỡng cực, mà OP KHÔNG hỏi về. Tôi sẽ để nó bây giờ, như là thông tin liên quan. nhưng thông tin thích hợp hơn liên quan đến sự trùng khớp hoàn hảo trong các biểu đồ không phân nhánh.

Về vấn đề này, có một khảo sát tốt của Propp phác thảo tiến trình (đến năm 1999). Một số ý tưởng trong bài viết đó và các liên kết liên quan có thể chứng minh là hữu ích. TL; DR là - nó khó khăn :)

--- Bắt đầu câu trả lời cũ

Lưu ý rằng những gì bạn yêu cầu làm là liệt kê tất cả các kết hợp hoàn hảo có thể có trên biểu đồ lưỡng cực. Có nhiều thuật toán khác nhau để thực hiện điều này, và đặc biệt một trong những thuật toán gần đây nhất là từ ISAAC 2001 .

Ý tưởng cơ bản là tìm một kết hợp hoàn hảo bằng cách sử dụng các luồng mạng và sau đó liên tục sửa đổi điều này bằng cách sử dụng các chu kỳ xen kẽ (xem bất kỳ chương sách giáo khoa thuật toán nào về các luồng mạng để biết thêm thông tin).


Biểu đồ lưỡng cực bao gồm hai bộ có nhãn đã cho [0, n) và có cạnh (i, j) khi và chỉ khi (i! = J)
Joe

nKn

2
các tính toán vĩnh viễn câu trả lời. nhưng OP muốn liệt kê chúng
Suresh

tất cả chúng đều đẳng cấu vì cấu trúc biểu đồ nên suy nghĩ về hoán vị áp dụng có thể là một ý tưởng tốt (nhưng vấn đề là nó sẽ tạo ra các bản sao).
Kaveh

4

Mỗi cặp bạn chọn sẽ loại bỏ hai hàng mà bạn không thể chọn nữa. Ý tưởng này có thể được sử dụng để thiết lập thuật toán đệ quy (trong Scala):

def combine(pairs : Seq[(Int,Int)]) : Seq[Seq[(Int, Int)]] = pairs match {
  case Seq() => Seq()
  case Seq(p) => Seq(Seq(p))
  case _ => {
    val combinations = pairs map { case (a,b) => {
      val others = combine(pairs filter { case (c,d) =>
        a != c && a != d && b != c && b != d
      })

      others map { s => ((a,b) +: s) }
    }}

    combinations.flatten map { _.sorted } distinct
  }
}

Điều này chắc chắn có thể được thể hiện một cách hiệu quả hơn. Cụ thể, ý tưởng không phải xem xét toàn bộ các hàng cho các kết hợp không được sử dụng bởi lệnh gọi đến filter.


Điều này cũng không trả về các kết hợp không bao gồm mọi số, nhưng không thể mở rộng vì không có cặp nào trong chuỗi ban đầu có thể mở rộng chúng? Nếu vậy, những kết hợp đó cần phải được lọc ra.
Carl Mummert

n2N

(0,1)n= =4

Vâng. Nhưng như tôi đã nói, câu trả lời của tôi liên quan đến kịch bản mà OP đề xuất, tức là không phải đầu vào tùy ý.
Raphael

Khi tôi đọc câu hỏi ban đầu, đó là về một cặp các cặp tùy ý, OP không bao giờ nói rằng tất cả các cặp đều có thể. Nhưng tôi đồng ý OP có thể rõ ràng hơn về điều đó.
Carl Mummert

4

Trong khi đã có nhiều câu trả lời đáng yêu cho câu hỏi, tôi nghĩ sẽ rất tốt nếu chỉ ra những mẹo cơ bản, chung chung, đằng sau chúng.

Việc tạo các kết hợp duy nhất sẽ dễ dàng hơn nhiều nếu bạn có thể có tổng thứ tự các yếu tố được kết hợp . Bằng cách này, tính duy nhất được đảm bảo nếu chúng tôi chỉ cho phép các kết hợp được sắp xếp. Không khó để tạo ra các kết hợp được sắp xếp - chỉ cần thực hiện tìm kiếm liệt kê lực lượng vũ phu thông thường, nhưng ở mỗi bước chỉ chọn các phần tử lớn hơn các phần tử đã chọn ở mỗi bước.

Sự phức tạp thêm trong vấn đề cụ thể này là mong muốn chỉ nhận được các kết hợp độ dài n / 2 (độ dài tối đa). Điều này không khó thực hiện nếu chúng ta quyết định chiến lược sắp xếp tốt. Ví dụ, như được chỉ ra trong câu trả lời của Carl Mummet, nếu chúng ta xem xét một loại từ vựng, (từ trên xuống, bên trái trong sơ đồ trong câu hỏi), chúng ta rút ra chiến lược luôn lấy phần tử tiếp theo sao cho chữ số đầu tiên của nó là số nhỏ nhất vẫn chưa sử dụng.

Chúng tôi cũng có thể mở rộng chiến lược này nếu chúng tôi muốn tạo các chuỗi có độ dài khác. Chỉ cần nhớ rằng bất cứ khi nào chúng ta chọn một phần tử tiếp theo có số đầu tiên không phải là phần tử nhỏ nhất có sẵn, chúng ta sẽ loại trừ một hoặc nhiều hàng phần tử xuất hiện trên phần tiếp theo được sắp xếp, do đó độ dài tối đa của phần mở đầu giảm theo.


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.