Berikut adalah bacokan pada suatu algoritma. Itu tidak sempurna, dan tergantung pada berapa banyak waktu yang ingin Anda habiskan untuk memurnikannya, mungkin ada beberapa keuntungan kecil yang akan dibuat.
Anggap Anda memiliki daftar tugas yang harus dilakukan oleh empat antrian. Anda tahu jumlah pekerjaan yang terkait dengan melakukan setiap tugas, dan Anda ingin keempat antrian mendapatkan jumlah pekerjaan yang hampir sama, sehingga semua antrian akan selesai pada waktu yang sama.
Pertama, saya akan mempartisi tugas menggunakan modulous, dipesan berdasarkan ukurannya, dari kecil ke besar.
SELECT [time], ROW_NUMBER() OVER (ORDER BY [time])%4 AS grp, 0
The ROW_NUMBER()pesanan setiap baris dengan ukuran, kemudian memberikan nomor baris, mulai dari 1. nomor baris ini diberikan sebuah "kelompok" (the grpkolom) secara round-robin. Baris pertama adalah grup 1, baris kedua adalah grup 2, lalu 3, keempat mendapat grup 0, dan seterusnya.
time ROW_NUMBER() grp
---- ------------ ---
1 1 1
10 2 2
12 3 3
15 4 0
19 5 1
22 6 2
...
Untuk kemudahan penggunaan, saya menyimpan timedan grpkolom dalam variabel tabel bernama @work.
Sekarang, kita dapat melakukan beberapa perhitungan pada data ini:
WITH cte AS (
SELECT *, SUM([time]) OVER (PARTITION BY grp)
-SUM([time]) OVER (PARTITION BY (SELECT NULL))/4 AS _grpoffset
FROM @work)
...
Kolom _grpoffsetadalah jumlah total timeper grpberbeda dari rata-rata "ideal". Jika total timesemua tugas adalah 1000 dan ada empat kelompok, idealnya ada total 250 dalam setiap kelompok. Jika grup berisi total 268, grup itu _grpoffset=18.
Idenya adalah untuk mengidentifikasi dua baris terbaik, satu di kelompok "positif" (dengan terlalu banyak pekerjaan) dan satu di kelompok "negatif" (dengan terlalu sedikit pekerjaan). Jika kita dapat bertukar grup pada dua baris itu, kita dapat mengurangi absolut _grpoffsetkedua grup.
Contoh:
time grp total _grpoffset
---- --- ----- ----------
3 1 222 40
46 1 222 40
73 1 222 40
100 1 222 40
6 2 134 -48
52 2 134 -48
76 2 134 -48
11 3 163 -21
66 3 163 -21
86 3 163 -21
45 0 208 24
71 0 208 24
92 0 208 24
----
=727
Dengan total total 727, setiap kelompok harus memiliki skor sekitar 182 untuk distribusinya menjadi sempurna. Perbedaan antara skor grup dan 182 adalah apa yang kami tempatkan di _grpoffsetkolom.
Seperti yang Anda lihat sekarang, di dunia terbaik, kita harus memindahkan sekitar 40 poin nilai baris dari grup 1 ke grup 2 dan sekitar 24 poin dari grup 3 ke grup 0.
Berikut kode untuk mengidentifikasi baris kandidat tersebut:
SELECT TOP 1 pos._row AS _pos_row, pos.grp AS _pos_grp,
neg._row AS _neg_row, neg.grp AS _neg_grp
FROM cte AS pos
INNER JOIN cte AS neg ON
pos._grpoffset>0 AND
neg._grpoffset<0 AND
--- To prevent infinite recursion:
pos.moved<4 AND
neg.moved<4
WHERE --- must improve positive side's offset:
ABS(pos._grpoffset-pos.[time]+neg.[time])<=pos._grpoffset AND
--- must improve negative side's offset:
ABS(neg._grpoffset-neg.[time]+pos.[time])<=ABS(neg._grpoffset)
--- Largest changes first:
ORDER BY ABS(pos.[time]-neg.[time]) DESC
) AS x ON w._row IN (x._pos_row, x._neg_row);
Saya menggabungkan diri dengan ekspresi tabel umum yang kami buat sebelumnya,: cteDi satu sisi, grup dengan positif _grpoffset, di sisi lain grup dengan yang negatif. Untuk lebih lanjut menyaring baris mana yang seharusnya cocok satu sama lain, swap dari baris sisi positif dan negatif harus ditingkatkan _grpoffset, yaitu membuatnya lebih dekat ke 0.
The TOP 1dan ORDER BYmemilih "terbaik" pertandingan swap pertama.
Sekarang, yang perlu kita lakukan hanyalah menambahkan UPDATE, dan memutarnya sampai tidak ada lagi optimasi yang ditemukan.
TL; DR - inilah pertanyaannya
Berikut kode lengkapnya:
DECLARE @work TABLE (
_row int IDENTITY(1, 1) NOT NULL,
[time] int NOT NULL,
grp int NOT NULL,
moved tinyint NOT NULL,
PRIMARY KEY CLUSTERED ([time], _row)
);
WITH cte AS (
SELECT 0 AS n, CAST(1+100*RAND(CHECKSUM(NEWID())) AS int) AS [time]
UNION ALL
SELECT n+1, CAST(1+100*RAND(CHECKSUM(NEWID())) AS int) AS [time]
FROM cte WHERE n<100)
INSERT INTO @work ([time], grp, moved)
SELECT [time], ROW_NUMBER() OVER (ORDER BY [time])%4 AS grp, 0
FROM cte;
WHILE (@@ROWCOUNT!=0)
WITH cte AS (
SELECT *, SUM([time]) OVER (PARTITION BY grp)
-SUM([time]) OVER (PARTITION BY (SELECT NULL))/4 AS _grpoffset
FROM @work)
UPDATE w
SET w.grp=(CASE w._row
WHEN x._pos_row THEN x._neg_grp
ELSE x._pos_grp END),
w.moved=w.moved+1
FROM @work AS w
INNER JOIN (
SELECT TOP 1 pos._row AS _pos_row, pos.grp AS _pos_grp,
neg._row AS _neg_row, neg.grp AS _neg_grp
FROM cte AS pos
INNER JOIN cte AS neg ON
pos._grpoffset>0 AND
neg._grpoffset<0 AND
--- To prevent infinite recursion:
pos.moved<4 AND
neg.moved<4
WHERE --- must improve positive side's offset:
ABS(pos._grpoffset-pos.[time]+neg.[time])<=pos._grpoffset AND
--- must improve negative side's offset:
ABS(neg._grpoffset-neg.[time]+pos.[time])<=ABS(neg._grpoffset)
--- Largest changes first:
ORDER BY ABS(pos.[time]-neg.[time]) DESC
) AS x ON w._row IN (x._pos_row, x._neg_row);