Hướng dẫn javascript n choose k - javascript n chọn k

Không bao giờ tính toán giai thừa, chúng phát triển quá nhanh. Thay vào đó tính toán kết quả bạn muốn. Trong trường hợp này, bạn muốn các số nhị thức, có cấu trúc hình học cực kỳ đơn giản: bạn có thể xây dựng hình tam giác của Pascal, vì bạn cần và thực hiện nó bằng số học đơn giản.

Bắt đầu với [1] và [1,1]. Hàng tiếp theo có [1] khi bắt đầu, [1+1] ở giữa và [1] ở cuối: [1,2,1]. Hàng tiếp theo: [1] Khi bắt đầu, tổng của hai thuật ngữ đầu tiên ở Spot 2, tổng của hai thuật ngữ tiếp theo ở vị trí ba và [1] ở cuối: [1,3,3,1]. Hàng tiếp theo: [1], sau đó 1+3 = 4, sau đó 3+3 = 6, sau đó 3+1 = 4, sau đó [1] ở cuối, v.v., v.v. Như bạn có thể thấy, không có giai thừa, logarit hoặc thậm chí nhân: chỉ bổ sung siêu nhanh với số nguyên sạch. Vì vậy, đơn giản, bạn có thể xây dựng một bàn tra cứu lớn bằng tay.

Và bạn nên.

Không bao giờ tính toán trong mã những gì bạn có thể tính toán bằng tay và chỉ bao gồm các hằng số để tra cứu ngay lập tức; Trong trường hợp này, việc viết ra bảng cho một cái gì đó xung quanh n = 20 là hoàn toàn tầm thường và sau đó bạn có thể chỉ sử dụng nó như "bắt đầu LUT" của bạn và có lẽ thậm chí không bao giờ truy cập vào các hàng cao.

Nhưng, nếu bạn cần chúng, hoặc nhiều hơn, vì bạn không thể xây dựng một bảng tra cứu vô hạn mà bạn thỏa hiệp: bạn bắt đầu với một LUT được chỉ định trước và một chức năng có thể "lấp đầy nó" chưa có trong đó:

// step 1: a basic LUT with a few steps of Pascal's triangle
const binomials = [
  [1],
  [1,1],
  [1,2,1],
  [1,3,3,1],
  [1,4,6,4,1],
  [1,5,10,10,5,1],
  [1,6,15,20,15,6,1],
  [1,7,21,35,35,21,7,1],
  [1,8,28,56,70,56,28,8,1],
  //  ...
];

// step 2: a function that builds out the LUT if it needs to.
module.exports = function binomial(n,k) {
  while(n >= binomials.length) {
    let s = binomials.length;
    let nextRow = [];
    nextRow[0] = 1;
    for(let i=1, prev=s-1; i<s; i++) {
      nextRow[i] = binomials[prev][i-1] + binomials[prev][i];
    }
    nextRow[s] = 1;
    binomials.push(nextRow);
  }
  return binomials[n][k];
};

Vì đây là một loạt các INT, nên dấu chân bộ nhớ là nhỏ. Đối với nhiều công việc liên quan đến nhị thức, chúng tôi thực tế thậm chí không cần nhiều hơn hai byte cho mỗi số nguyên, biến đây thành một bảng tra cứu rất nhỏ: chúng tôi không cần nhiều hơn 2 byte cho đến khi bạn cần Binomials cao hơn N = 19 và Bảng tra cứu đầy đủ lên đến n = 19 chiếm 380 byte. Điều này không là gì so với phần còn lại của chương trình của bạn. Ngay cả khi chúng tôi cho phép ints 32 bit, chúng tôi có thể lên tới n = 35 trong một byte đơn thuần.

Vì vậy, việc tra cứu là nhanh: hoặc O (hằng số) cho các giá trị được tính toán trước đó, (n*(n+1))/2 bước nếu chúng ta không có LUT nào cả (trong ký hiệu O lớn, đó sẽ là o (n²), nhưng Big O ký hiệu hầu như không bao giờ là biện pháp phức tạp đúng), và ở đâu đó ở giữa các thuật ngữ chúng ta cần chưa có trong LUT. Chạy một số điểm chuẩn cho ứng dụng của bạn, điều này sẽ cho bạn biết LUT ban đầu của bạn nên lớn như thế nào, chỉ đơn giản là mã cứng (nghiêm túc. Đây là những hằng số, chúng chính xác là loại giá trị nên được mã hóa cứng) và giữ cho máy phát điện xung quanh chỉ trong trường hợp.

Tuy nhiên, hãy nhớ rằng bạn đang ở vùng đất JavaScript và bạn bị hạn chế bởi loại số JavaScript: Số nguyên chỉ tăng lên 2^53, ngoài tính thuộc tính số nguyên (mỗi n đều có một m=n+1 khác biệt như vậy m-n=1) không được đảm bảo . Tuy nhiên, điều này hầu như không phải là một vấn đề: một khi chúng tôi đạt đến giới hạn đó, chúng tôi đang xử lý các hệ số nhị thức mà bạn thậm chí không bao giờ nên sử dụng.

Kiểm tra năng suất.

Xin lưu ý câu trả lời này là về hiệu suất không phức tạp.

Tại sao kết quả hiệu suất kỳ lạ?

Lý do cho các kết quả khác nhau là do thử nghiệm kém.

JavaScript Không giống như hầu hết các ngôn ngữ không bao giờ được biên soạn đầy đủ. Trong nền có các công cụ tối ưu hóa nhiều cấp độ xem xét mã chạy và áp dụng tối ưu hóa khi mã đang chạy.

Tuy nhiên, trình tối ưu hóa có một hình phạt rõ ràng nhất khi mã được chạy đầu tiên. Nó cũng có thể tối ưu hóa một số mã nhanh hơn những mã khác.

Để so sánh các chức năng, bạn cần để trình tối ưu hóa thực hiện công việc của mình (mỗi trình duyệt có các phương thức tối ưu hóa rất khác nhau). Chỉ sau khi chức năng được chạy nhiều lần, bạn mới có thể bắt đầu nhận được kết quả chính xác.

Khi tôi kiểm tra mã của bạn, tôi thấy rằng phương thức "quay lại" trung bình chậm hơn 5%.

Kiểm tra ví dụ

Lưu ý rằng thời gian được điều chỉnh để đảm bảo độ phân giải thời gian tốt. Kết quả là vô nghĩa như số liệu hiệu suất tuyệt đối. Họ chỉ có ý nghĩa so sánh trong một lần chạy thử.

Kiểm tra năng suất.

Xin lưu ý câu trả lời này là về hiệu suất không phức tạp.

Tại sao kết quả hiệu suất kỳ lạ?

Lý do cho các kết quả khác nhau là do thử nghiệm kém.

JavaScript Không giống như hầu hết các ngôn ngữ không bao giờ được biên soạn đầy đủ. Trong nền có các công cụ tối ưu hóa nhiều cấp độ xem xét mã chạy và áp dụng tối ưu hóa khi mã đang chạy.

function combinations(n, k) {
    let result= [];

  function recurse(start, combos) {
    if(combos.length === k) {
      return result.push(combos.slice());
    }
    if(combos.length + (n - start + 1) < k){
      return;
    }
    recurse(start + 1, combos);
    combos.push(start);
    recurse(start + 1, combos);
    combos.pop();
  }

  recurse(1, []);
  return result;
}

Tuy nhiên, trình tối ưu hóa có một hình phạt rõ ràng nhất khi mã được chạy đầu tiên. Nó cũng có thể tối ưu hóa một số mã nhanh hơn những mã khác.

Để so sánh các chức năng, bạn cần để trình tối ưu hóa thực hiện công việc của mình (mỗi trình duyệt có các phương thức tối ưu hóa rất khác nhau). Chỉ sau khi chức năng được chạy nhiều lần, bạn mới có thể bắt đầu nhận được kết quả chính xác.

Khi tôi kiểm tra mã của bạn, tôi thấy rằng phương thức "quay lại" trung bình chậm hơn 5%.

Đoạn trích là một thử nghiệm hiệu suất giảm và bẩn sẽ kiểm tra các chức năng trong nhiều chu kỳ và đưa ra kết quả trung bình của thời gian chức năng.

Hàm sửa đổi luôn là nhanh nhất.

function combinationsAV2(n, k) {
  const result= [];
  const combos = [];
  const recurse = start => {
    if (combos.length + (n - start + 1) < k) { return }
    recurse(start + 1);
    combos.push(start);
    if(combos.length === k) {     
       result.push(combos.slice());
    }else if(combos.length + (n - start + 2) >= k){
       recurse(start + 1);
    }
    combos.pop();     
  }
  recurse(1, combos);
  return result;
}

Kiểm tra ví dụ

Lưu ý rằng thời gian được điều chỉnh để đảm bảo độ phân giải thời gian tốt. Kết quả là vô nghĩa như số liệu hiệu suất tuyệt đối. Họ chỉ có ý nghĩa so sánh trong một lần chạy thử.