Weintrinker Problem

Neue Frage »

Auf diesen Beitrag antworten »
SilverG6 Weintrinker Problem

Meine Frage:
Die Aufgabe :
Es treffen sich 2 Wanderer. Der Erste besitzt einen Krug mit 8 Liter Wein Inhalt, dass Gesamtvolumen dieses Krugs ist ebenfalls 8 Liter. Der Zweite besitzt 2 Krüge mit jeweils 5 und 3 Liter Füllvolumen, jedoch besitzt er noch keinen Wein. Der erstere möchte mit dem 2. so teilen das sie beide gleich viel Wein besitzen.

Die beiden besitzen leider keine Hilfsmittel zum abmessen. Das bedeutet, dass sie nur die Krüge komplett umkippen können bis einer leer oder der zu befüllende Voll ist.

Ich soll dazu nun ein Programm schreiben welches auch für allgemein gültige Fälle die schnellste Lösung angibt.

Meine Ideen:
Ich brauche keine Hilfe beim programmieren, an was es mir fehlt ist ein richtiger Lösungsansatz. Irgendwie scheint es mir was mit Differenzen zusammenzuhängen. Ich hab schon ziemlich lange daran gesessen und weiß das dies nur ein dürftiger eigener Ansatz ist.
Meine Idee ist nun, dass man immer die Differenz zwischen den beiden Wanderern betrachtet und immer so umfüllt man beim umfüllen die Differenz entweder verringert oder halt die kleinst mögliche Erhöhung entsteht.
Bsp. Person A besitzt 2 Krüge mit den Volumen 10(a) und 8(b) Liter.
Krug a ist mit 10 Liter Wein gefüllt.
Krug b ist mit 4 Liter Wein gefüllt.
Person B besitzt auch 2 Krüge mit den Volumen 11(c) und 7(d) Liter.
Krug c ist mit 4 Liter Wein gefüllt.
Krug d ist mit 6 Liter Wein gefüllt.

Die Differenz beträgt 4 Liter
Person A besitzt 14 Liter und Person B 10 also muss Person A abgeben
Möglichkeiten des umfüllens :
a --> c = 7 Liter Austausch
a --> d = 1 Liter Austausch
b --> c = 4 Liter Austausch
b --> d = 1 Liter Austausch

Man muss aber auch nur 2 Liter ausgleichen, da man sonst ja die Differenz einfach halten würde also muss immer die hälfte der Differenz ausgeglichen werden.

also würde ich jetzt einfach a --> d füllen
das würde ich dann einfach in eine Schleife mit Abbruchbedingung stecken und fertig \(^.^)/ /(._.)\

Ich danke schonmal für Hilfe !
mit freundlichen Grüßen Franz
 
Auf diesen Beitrag antworten »
SilverG6 RE: Weintrinker Problem

Was mir und einem Freund noch aufgefallen ist. Es muss bei jeder Differenzveränderung zwischen den Weinmengen der Wanderer ein internes umschütten erfolgen bevor sie wieder gegenseitig umgießen.
Auf diesen Beitrag antworten »
Karlito

Hallo,

ich würde die Differenzen außer Acht lassen und einfach nur eine Breitensuche implementieren mit der Abbruchbedingung, dass es keine weiteren Möglichkeiten mehr gibt oder die Differenz null ist. Das Problem ist nämlich, dass die Differenz zwischenzeitlich wieder höher werden kann.

Für die Breitensuche würde ich einfach eine Warteschlange nehmen und von jeder Konfiguration jede Folgekonfiguration in die Warteschlange einfügen (jede Umfüllmöglichkeit). Dabei muss man sich natürlich zu jedem Knoten im Suchbaum den Pfad merken. Weiterhin braucht es eine Liste, in welcher alle schon bekannten Konfigurationen gespeichert sind um doppelte Expansion des Knotens mit einer bekannten Konfiguration zu vermeiden und zu garantieren, dass das Programm terminiert, falls es keine Lösung gibt.

Gruß,

Karlito
Auf diesen Beitrag antworten »
SilverG6

Also meinst du ich sollte es mit Backtracking und probieren versuchen ?
Die Abbruchbedingung wäre dann also wenn eine gleiche Verteilung wie irgendwann vorher schon auftritt ?

Ja das ist uns auch schon aufgefallen das war etwas verwirrend ^^

Aber soll ich das ganze dann einfach per Zufall machen ?
Ich hätte eigentlich gerne einen Algorithmus ._. großes Grinsen So wie bei einem Labyrinth das ich irgendwie definieren kann das man gegen keine Wände läuft ^^

Aber ich glaube ich weiß ungefähr was du meinst smile
ich versuche das mal umzusetzen !
DANKE <3

Btw bleibt das Forum hier so falls ich nachträglich dann noch fragen hätte ?

mfg franz
 
Auf diesen Beitrag antworten »
eulerscheZahl

Du fängst mit einem Umfüllvorgang an, speicherst das Ergebnis, kehrst zur Lage von vor dem Umfüllen zurück und versuchst einen anderen Zug, bis du für eine Stellung alle möglichen Züge getestet hast.
Diese Stellungen haben ermöglichen wiederum Züge, die du abarbeiten musst.
Eine Möglichkeit ist Rekursion: du entscheidest dich am Anfang für einen Pfad und klapperst den komplett ab. Dann machst du das selbe für einen anderen Anfangszug.
Alternativ (das dürfte effizienter sein): Du speicherst alle mit einem Zug erreichbaren Ergebnisse. Für jedes von diesen machst du einen weiteren Zug und speicherst das dann wieder (wenn du diese Stellung nicht früher schon erreicht hattest). Von den Ergebnissen aus Runde 2 kannst du die für den 3. Zug ermitteln. Zusätzlich solltest du für jeden Zug noch speichern, wo du hergekommen bist, damit du nicht nur sagen kannst, wie viele Züge nötig sind, sondern auch welche.

Und hier wird kein Thread geschlossen, solange es kein Spam ist. Kannst dich gerne auch nach ein paar Wochen wieder melden.
Auf diesen Beitrag antworten »
Karlito

Hallo,

Zitat:
Original von SilverG6
Also meinst du ich sollte es mit Backtracking und probieren versuchen ?

Ja.

Zitat:
Original von SilverG6
Die Abbruchbedingung wäre dann also wenn eine gleiche Verteilung wie irgendwann vorher schon auftritt ?


Nein. Du hast 2 Gruppen an Krügen in der allgemeinen Variante, oder? Ziel ist einfach, dass beide Gruppen die gleiche Menge Wein haben. D.h. die Differenz der Menge Wein zwischen beiden Gruppen ist 0.

Zitat:
Original von SilverG6
Aber soll ich das ganze dann einfach per Zufall machen ?
Ich hätte eigentlich gerne einen Algorithmus ._. großes Grinsen So wie bei einem Labyrinth das ich irgendwie definieren kann das man gegen keine Wände läuft ^^


Natürlich nicht per Zufall. Du kannst doch Systematisch vorgehen. Du hast einen Knoten im Suchbaum. Dann wählst du einen Krug aus und schüttest der Reihe nach in alle anderen Krüge um. Das wiederholst du für jeden nicht leeren Krug.
Was den Algorithmus angeht: da wüsste ich gerade keinen Weg. Schau dir mal A* an. Die Schwierigkeit dabei ist sicher eine gute Heuristik zu finden.

Zitat:
Original von SilverG6
Btw bleibt das Forum hier so falls ich nachträglich dann noch fragen hätte ?


Der Link bleibt gleich, die Rangfolge auf der "Portalseite" ändert sich sobald ein neuer Beitrag verfasst wird. Im Forum selbst kannst Du den Beitrag jedoch immer wieder finden und weitere Fragen anhängen.

Gruß,

Karlito
Auf diesen Beitrag antworten »
Karlito

Hallo euler,

Zitat:
Original von eulerscheZahl
Eine Möglichkeit ist Rekursion: du entscheidest dich am Anfang für einen Pfad und klapperst den komplett ab. Dann machst du das selbe für einen anderen Anfangszug.


Eine Rekursion ist hier ungeeignet ist, da man mit Rekursion üblicherweise eine Tiefensuche realisiert. Laut Aufgabenstellung ist jedoch die schnellste Lösung gefragt, auf welche man nur stößt, wenn man alle Lösungen der Tiefensuche vergleicht oder Breitensuche verwendet und die erste Lösung wiedergibt. Würde bedeuten, dass man bei der Rekursion den gesamten Suchbaum ablaufen muss um dann alle Lösungen zu vergleichen (unoptimiert). Oder übersehe ich etwas?

Gruß,

Karlito
Auf diesen Beitrag antworten »
eulerscheZahl

Deshalb
Zitat:
Alternativ (das dürfte effizienter sein): ...

Man könnte das Ganze etwas optimieren, wenn man zu jeden Schritt die Anzahl der benötigten Züge und die Folgezustände speichert (um die Zahl der nötigen Züge dann für den gesamten Baum zu verringern). Aber wie gesagt: Breitensuche ist effizienter.
Vielleicht habe ich morgen etwas langeweile und schreibe ein paar Zeilen, mal sehen.
Auf diesen Beitrag antworten »
eulerscheZahl

Entweder habe ich Unsinn getrieben, oder dein zweites Beispiel ist nicht lösbar.
Für das erste erhalte ich: jeweils Füllstand(maximal)
code:
1:
2:
3:
4:
5:
6:
7:
8:
8(8) 0(5) 0(3) 
3(8) 5(5) 0(3) 
3(8) 2(5) 3(3) 
6(8) 2(5) 0(3) 
6(8) 0(5) 2(3) 
1(8) 5(5) 2(3) 
1(8) 4(5) 3(3) 
4(8) 4(5) 0(3)


hier mein Code (C#, getestet mit MonoDevelop):
code:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
using System;
using System.Collections.Generic;

namespace Weintrinker
{
	class MainClass
	{
		private static int encode (int[] curr, int[] max)
		{
			int result = 0;
			for (int i = 0; i < curr.Length - 1; i++) { //no need to add last item, as sum = const
				result *= max [i] + 1;
				result += curr [i];
			}
			return result;
		}

		private static int[] decode (int curr, int[] max, int sum)
		{
			int[] result = new int[max.Length];
			int sum_tmp = 0;
			for (int i = max.Length - 2; i >= 0; i--) {
				result [i] = curr % (max [i] + 1);
				sum_tmp += result [i];
				curr /= (max [i] + 1);
			}
			result [result.Length - 1] = sum - sum_tmp;
			return result;
		}

		private static List<int> getFollowing (int state, int[] max, int sum, List<int[]> index, out bool success)
		{
			List<int> result = new List<int> ();
			success = false;
			int[] curr = decode (state, max, sum);
			for (int source = 0; source < curr.Length; source++) {
				if (curr [source] == 0)
					continue;
				for (int dest = 0; dest < curr.Length; dest++) {
					if (curr [dest] == max [dest] || dest == source)
						continue;
					int fill = Math.Min (curr [source], max [dest] - curr [dest]);
					int[] nextState = (int[])curr.Clone ();
					nextState [source] -= fill;
					nextState [dest] += fill;
					result.Add (encode (nextState, max));
					//test if solution found
					int sumFirst = 0;
					foreach (int i in index[0])
						sumFirst += nextState [i];
					bool allSumsEqual = true;
					for (int i = 1; i < index.Count && allSumsEqual; i++) {
						int sumCurrent = 0;
						foreach (int j in index[i])
							sumCurrent += nextState [j];
						if (sumCurrent != sumFirst)
							allSumsEqual = false;
					}
					if (allSumsEqual) {
						success = true;
						return result; //solution is the last item in the list
					}
				}
			}
			return result;
		}

		private static string Solve (List<int[]> listMax, List<int[]> listCurrent)
		{
			int n = 0;
			int sum = 0;
			foreach (int[] personMax in listMax)
				n += personMax.Length;
			int[] max = new int[n];
			int[] curr = new int[n];
			List<int[]> index = new List<int[]> ();
			int currIndex = 0;
			for (int i = 0; i < listMax.Count; i++) {
				index.Add (new int[listMax [i].Length]);
				for (int j = 0; j < listMax[i].Length; j++) {
					max [currIndex] = listMax [i] [j];
					curr [currIndex] = listCurrent [i] [j];
					sum += curr [currIndex];
					index [i] [j] = currIndex++;
				}
			}
			//states[steps to reach state] < currentState, previousState>
			List<Dictionary<int, int>> states = new List<Dictionary<int, int>> ();
			states.Add (new Dictionary<int, int> ());
			states [0].Add (encode (curr, max), 0);
			bool success = false;
			int[] path;
			for (int moves = 1; states[moves-1].Count > 0; moves++) {
				states.Add (new Dictionary<int, int> ());
				foreach (KeyValuePair<int, int> entry in states[moves - 1]) {
					List<int> followingStates = getFollowing (entry.Key, max, sum, index, out success);
					foreach (int next in followingStates) {
						bool add = true;
						for (int j = 0; j <= moves; j++)
							if (states [j].ContainsKey (next)) {
								add = false;
								continue;
							}
						if (add) 
							states [moves].Add (next, entry.Key);
					}
					if (success) {
						path = new int[moves + 1];
						path [path.Length - 1] = followingStates [followingStates.Count - 1];
						goto solutionFound;
					}
				}
			}
			return "no solution found\n";
			solutionFound:
			for (int i = path.Length - 2; i >= 0; i--)
				path [i] = states [i + 1] [path [i + 1]];
			string solution = "";
			for (int i = 0; i < path.Length; i++) {
				int[] vals = decode (path [i], max, sum);
				for (int j= 0; j < vals.Length; j++)
					solution += vals [j] + "(" + max [j] + ") ";
				solution += "\n";
			}
			return solution;
		}

		public static void Main (string[] args)
		{
			List<int[]> max = new List<int[]> ();
			List<int[]> curr = new List<int[]> ();
			max.Add (new int[] { 8 });
			max.Add (new int[] { 5, 3 });
			curr.Add (new int[] { 8 });
			curr.Add (new int[] { 0, 0 });
			Console.Write (Solve (max, curr));
			max.Clear (); curr.Clear ();
			max.Add (new int[] { 10, 8 });
			max.Add (new int[] { 11, 7 });
			curr.Add (new int[] { 10, 4 });
			curr.Add (new int[] { 4, 6 });
			Console.Write (Solve (max, curr));
		}
	}
}
Auf diesen Beitrag antworten »
Karlito

Nice work! Daumen hoch

So. Und jetzt noch mal in C, Haskell und Prolog großes Grinsen

Gruß,

Karlito
Auf diesen Beitrag antworten »
eulerscheZahl

Aber nur, solange [latex]\prod\limits_{i=1}^{Anzahl_{Eimer}-1}{(1+v_{max,i)}} \leq int_{max}[/latex]. Dann kann ich das int mit der Funktion decode nicht zurück in int[] umrechnen, würde wohl ohnehin Performaceprobleme geben. Immerhin wird mein Code auch mit mehr als zwei Eimern pro Person und mit mehr als zwei Personen fertig.
 
Neue Frage »
Antworten »


Verwandte Themen