123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372 |
- import { JavaObject, cast_java_lang_Object } from '../../java/lang/JavaObject';
- import { Random, cast_java_util_Random } from '../../java/util/Random';
- import { StringBuilder, cast_java_lang_StringBuilder } from '../../java/lang/StringBuilder';
- import { JavaLong, cast_java_lang_Long } from '../../java/lang/JavaLong';
- import { JavaString, cast_java_lang_String } from '../../java/lang/JavaString';
- import { Arrays, cast_java_util_Arrays } from '../../java/util/Arrays';
- import { System, cast_java_lang_System } from '../../java/lang/System';
- export class KursblockungMatrix extends JavaObject {
- private readonly RND : Random = new Random();
- private readonly matrix : Array<Array<number>>;
- private readonly rows : number;
- private readonly cols : number;
- private readonly permR : Array<number>;
- private readonly permC : Array<number>;
- private readonly r2c : Array<number>;
- private readonly c2r : Array<number>;
- private readonly besuchtR : Array<boolean>;
- private readonly abgearbeitetC : Array<boolean>;
- private readonly vorgaengerCzuR : Array<number>;
- private readonly queueR : Array<number>;
- private readonly potentialR : Array<number>;
- private readonly potentialC : Array<number>;
- private readonly distanzC : Array<number>;
- /**
- * Erzeugt eine neue Matrix mit {@code rows} Zeilen und {@code cols} Spalten.
- *
- * @param rows Die Anzahl der Zeilen der Matrix.
- * @param cols Die Anzahl der Spalten der Matrix.
- */
- public constructor(rows : number, cols : number) {
- super();
- this.rows = rows;
- this.cols = cols;
- this.matrix = [...Array(rows)].map(e => Array(cols).fill(0));
- this.permR = Array(rows).fill(0);
- this.permC = Array(cols).fill(0);
- this.r2c = Array(rows).fill(0);
- this.c2r = Array(cols).fill(0);
- this.besuchtR = Array(rows).fill(false);
- this.abgearbeitetC = Array(cols).fill(false);
- this.vorgaengerCzuR = Array(cols).fill(0);
- this.queueR = Array(rows).fill(0);
- this.potentialR = Array(rows).fill(0);
- this.potentialC = Array(cols).fill(0);
- this.distanzC = Array(cols).fill(0);
- KursblockungMatrix.initialisiere(this.permR);
- KursblockungMatrix.initialisiere(this.permC);
- }
- /**
- * Berechnet zur aktuellen Matrix ein maximales bipartites Matching. Die Methode
- * geht davon aus, dass in der Matrix ausschließlich die Werte 0 und 1
- * vorkommen. Werte ungleich 0 werden andernfalls als 1 (eine Kante im Graphen)
- * interpretiert. Nichtquadratische Matrizen sind erlaubt. Das Ergebnis der
- * Methode ist eine größtmögliche Zeilen- zu Spaltenzuordnung. Der Algorithmus
- * hat eine Laufzeit von O(n³).
- *
- * @param nichtdeterministisch definiert, ob das Ergebnis zufällig sein soll,
- * falls es mehrere optimale Lösungen gibt.
- *
- * @return die Zeilen- zu Spaltenzuordnung, negative Werte entsprechen einer
- * Nichtzuordnung.
- */
- public gibMaximalesBipartitesMatching(nichtdeterministisch : boolean) : Array<number> {
- Arrays.fill(this.r2c, -1);
- Arrays.fill(this.c2r, -1);
- this.initialisierPermRundPermC(nichtdeterministisch);
- for (let pseudoR : number = 0; pseudoR < this.rows; pseudoR++){
- let r : number = this.permR[pseudoR];
- Arrays.fill(this.besuchtR, false);
- Arrays.fill(this.vorgaengerCzuR, -1);
- let queue_first : number = 0;
- let queue_last : number = 0;
- this.queueR[queue_last] = r;
- queue_last++;
- this.besuchtR[r] = true;
- while (queue_first < queue_last) {
- let vonR : number = this.queueR[queue_first];
- queue_first++;
- for (let pseudoC : number = 0; pseudoC < this.cols; pseudoC++){
- let ueberC : number = this.permC[pseudoC];
- if ((this.matrix[vonR][ueberC] !== 0) && (this.r2c[vonR] !== ueberC)) {
- let zuR : number = this.c2r[ueberC];
- if (zuR === -1) {
- this.vorgaengerCzuR[ueberC] = vonR;
- for (let c2 : number = ueberC; c2 >= 0; ){
- let r2 : number = this.vorgaengerCzuR[c2];
- let saveC : number = this.r2c[r2];
- this.c2r[c2] = r2;
- this.r2c[r2] = c2;
- c2 = saveC;
- }
- queue_last = queue_first;
- break;
- }
- if (this.besuchtR[zuR] === false) {
- this.besuchtR[zuR] = true;
- this.queueR[queue_last] = zuR;
- queue_last++;
- this.vorgaengerCzuR[ueberC] = vonR;
- }
- }
- }
- }
- }
- return this.r2c;
- }
- /**
- * Berechnet zur aktuellen Matrix ein minimales gewichtetes Matching. Die
- * Methode geht davon aus, dass in der Matrix ganzzahlige Werte vorkommen, d.h.
- * es existiert eine Kante von jedem linken Knoten zu jedem rechten Knoten.
- * Negative Werte und nichtquadratische Matrizen sind erlaubt. Zur Berechnung
- * eines maximalen Matching kann man vorher alle Zellenwerte negieren. Das
- * Ergebnis der Methode ist eine Zeilen- zu Spaltenzuordnung, deren Summe
- * minimal ist. Der Algorithmus verwendet mehrere Runden eines SSSP-Algorithmus
- * (Dijkstra). Damit dies bei negativen Werten funktioniert, werden die Kanten
- * mit Hilfe von Knoten-Potentialen umgewichtet. Der Algorithmus hat eine
- * Laufzeit von O(n³).
- *
- * @see <a href= "https://en.wikipedia.org/wiki/Shortest_path_problem">Wikipedia
- * - Shortest_path_problem</a>
- *
- * @see <a href= "https://en.wikipedia.org/wiki/Johnson%27s_algorithm">Wikipedia
- * - Johnsons Algorithm</a>
- *
- * @param nichtdeterministisch definiert, ob das Ergebnis zufällig sein soll,
- * falls es mehrere optimale Lösungen gibt.
- *
- * @return die Zeilen- zu Spaltenzuordnung, negative Werte entsprechen einer
- * Nichtzuordnung.
- */
- public gibMinimalesBipartitesMatchingGewichtet(nichtdeterministisch : boolean) : Array<number> {
- Arrays.fill(this.r2c, -1);
- Arrays.fill(this.c2r, -1);
- Arrays.fill(this.potentialR, 0);
- Arrays.fill(this.potentialC, 0);
- this.initialisierPermRundPermC(nichtdeterministisch);
- if (this.rows <= this.cols) {
- for (let r : number = 0; r < this.rows; r++){
- let min : number = this.matrix[r][0] + this.potentialR[r] - this.potentialC[0];
- for (let c : number = 0; c < this.cols; c++){
- let kante : number = this.matrix[r][c] + this.potentialR[r] - this.potentialC[c];
- min = Math.min(min, kante);
- }
- this.potentialR[r] -= min;
- }
- }
- if (this.cols <= this.rows) {
- for (let c : number = 0; c < this.cols; c++){
- let min : number = this.matrix[0][c] + this.potentialR[0] - this.potentialC[c];
- for (let r : number = 0; r < this.rows; r++){
- let kante : number = this.matrix[r][c] + this.potentialR[r] - this.potentialC[c];
- min = Math.min(min, kante);
- }
- this.potentialC[c] += min;
- }
- }
- let dijkstraRunden : number = Math.min(this.rows, this.cols);
- Arrays.fill(this.abgearbeitetC, false);
- for (let ir : number = 0; ir < this.rows; ir++){
- let r : number = this.permR[ir];
- for (let ic : number = 0; ic < this.cols; ic++){
- let c : number = this.permC[ic];
- if (!this.abgearbeitetC[c]) {
- let kante : number = this.matrix[r][c] + this.potentialR[r] - this.potentialC[c];
- if (kante === 0) {
- this.r2c[r] = c;
- this.c2r[c] = r;
- this.abgearbeitetC[c] = true;
- dijkstraRunden--;
- break;
- }
- }
- }
- }
- for (let dijkstraRunde : number = 0; dijkstraRunde < dijkstraRunden; dijkstraRunde++){
- for (let ic : number = 0; ic < this.cols; ic++){
- let c : number = this.permC[ic];
- this.vorgaengerCzuR[c] = -1;
- this.abgearbeitetC[c] = false;
- this.distanzC[c] = Number.MAX_VALUE;
- for (let ir : number = 0; ir < this.rows; ir++){
- let r : number = this.permR[ir];
- if (this.r2c[r] < 0) {
- let kante : number = this.matrix[r][c] + this.potentialR[r] - this.potentialC[c];
- if (kante < this.distanzC[c]) {
- this.distanzC[c] = kante;
- this.vorgaengerCzuR[c] = r;
- }
- }
- }
- }
- let endknotenC : number = -1;
- for (let dijkstraZyklus : number = 0; dijkstraZyklus < this.cols; dijkstraZyklus++){
- let fromC : number = 0;
- for (let ic : number = 0; ic < this.cols; ic++){
- let c : number = this.permC[ic];
- if (!this.abgearbeitetC[c]) {
- if ((this.abgearbeitetC[fromC]) || (this.distanzC[c] < this.distanzC[fromC])) {
- fromC = c;
- }
- }
- }
- this.abgearbeitetC[fromC] = true;
- let overR : number = this.c2r[fromC];
- if (overR >= 0) {
- for (let ic : number = 0; ic < this.cols; ic++){
- let toC : number = this.permC[ic];
- let kante : number = this.matrix[overR][toC] + this.potentialR[overR] - this.potentialC[toC];
- let distance : number = this.distanzC[fromC] + kante;
- if (distance < this.distanzC[toC]) {
- this.distanzC[toC] = distance;
- this.vorgaengerCzuR[toC] = overR;
- }
- }
- } else {
- if (endknotenC < 0) {
- endknotenC = fromC;
- }
- }
- }
- let currentC : number = endknotenC;
- while (currentC >= 0) {
- let prevR : number = this.vorgaengerCzuR[currentC];
- let prevC : number = this.r2c[prevR];
- this.r2c[prevR] = currentC;
- this.c2r[currentC] = prevR;
- currentC = prevC;
- }
- for (let r : number = 0; r < this.rows; r++){
- let c : number = this.r2c[r];
- if (c >= 0) {
- let kante : number = this.matrix[r][c] + this.potentialR[r] - this.potentialC[c];
- this.potentialR[r] += this.distanzC[c] - kante;
- this.potentialC[c] += this.distanzC[c];
- }
- }
- }
- return this.r2c;
- }
- /**
- * Interne Methode zum Permutieren oder Initialisieren der Arrays
- * {@link KursblockungMatrix#permR} und {@link KursblockungMatrix#permC}.
- *
- * @param nichtdeterministisch falls {@code true} werden
- * {@link KursblockungMatrix#permR} und
- * {@link KursblockungMatrix#permC} permutiert,
- * sonst initialisiert.
- */
- private initialisierPermRundPermC(nichtdeterministisch : boolean) : void {
- if (nichtdeterministisch) {
- this.permutiere(this.permR);
- this.permutiere(this.permC);
- } else {
- KursblockungMatrix.initialisiere(this.permR);
- KursblockungMatrix.initialisiere(this.permC);
- }
- }
- /**
- * Interne Methode zum Initialisieren eines Arrays so, dass das Array mit den
- * Zahlen {@code 0,1,2...} gefüllt wird.
- *
- * @param perm Das Array, welches mit den Zahlen {@code 0,1,2...} gefüllt wird.
- */
- private static initialisiere(perm : Array<number>) : void {
- let laenge : number = perm.length;
- for (let i : number = 0; i < laenge; i++){
- perm[i] = i;
- }
- }
- /**
- * Interne Methode zum zufälligen Permutieren eines Arrays.
- *
- * @param perm Das Array, dessen Inhalt zufällig permutiert wird.
- */
- private permutiere(perm : Array<number>) : void {
- let laenge : number = perm.length;
- for (let i : number = 0; i < laenge; i++){
- let j : number = this.RND.nextInt(laenge);
- let saveI : number = perm[i];
- let saveJ : number = perm[j];
- perm[i] = saveJ;
- perm[j] = saveI;
- }
- }
- /**
- * Erlaubt Zugriff auf den Inhalt des Arrays.
- *
- * @return Die Array-Referenz.
- */
- public getMatrix() : Array<Array<number>> {
- return this.matrix;
- }
- /**
- * Erzeugt String-Ausgabe des Arrays sowie der Zeilen-zu-Spalten-Zuordnung
- * {@link KursblockungMatrix#r2c}. Diese Methode ist für Debug-Zwecke gedacht.
- *
- * @param kommentar Ein Kommentar der über der Matrix angezeigt wird.
- * @param zellenbreite Die Breite bei der Ausgabe der Zelle.
- * @param mitKnotenPotential Falls {@code true}, werden die Kantenwerte
- * umgewichtet entsprechenden der Knotenpotentiale,
- * andernfalls bleiben die Kantenwerte unverändert.
- *
- * @return Eine String-Representation der Matrix.
- */
- public convertToString(kommentar : String, zellenbreite : number, mitKnotenPotential : boolean) : String {
- let sb : StringBuilder = new StringBuilder();
- sb.append(kommentar.valueOf() + System.lineSeparator().valueOf());
- for (let r : number = 0; r < this.rows; r++){
- for (let c : number = 0; c < this.cols; c++){
- let wert : number = mitKnotenPotential ? this.matrix[r][c] + this.potentialR[r] - this.potentialC[c] : this.matrix[r][c];
- let sWert : String | null = "" + wert;
- while (sWert.length < zellenbreite)
- sWert = " " + sWert.valueOf();
- let sZusatz : String = this.r2c[r] === c ? "*" : " ";
- sb.append(sWert.valueOf() + sZusatz.valueOf());
- }
- sb.append("\n");
- }
- sb.append("r2c = " + Arrays.toString(this.r2c).valueOf());
- sb.append("\n");
- return sb.toString();
- }
- /**
- * Füllt die Matrix mit ganzzahligen zufälligen Zahlenwerten aus dem Intervall
- * {@code [von;bis]}.
- *
- * @param von Der kleinstmögliche zufällige Wert (inklusive).
- * @param bis Der größtmögliche zufällige Wert (inklusive).
- */
- public fuelleMitZufallszahlenVonBis(von : number, bis : number) : void {
- for (let r : number = 0; r < this.rows; r++){
- for (let c : number = 0; c < this.cols; c++){
- this.matrix[r][c] = this.RND.nextInt(bis - von + 1) + von;
- }
- }
- }
- isTranspiledInstanceOf(name : string): boolean {
- return ['de.nrw.schule.svws.core.kursblockung.KursblockungMatrix'].includes(name);
- }
- }
- export function cast_de_nrw_schule_svws_core_kursblockung_KursblockungMatrix(obj : unknown) : KursblockungMatrix {
- return obj as KursblockungMatrix;
- }
|