using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MathModTasks { struct Element { public int Delivery { get; set; } public int Value { get; set; } public static int FindMinElement(int a, int b) { if (a > b) return b; if (a == b) { return a; } else return a; } } /// /// Решение трансопортной задачи методом потенциалов с использованием распределения по мин. элементу /// class PotentialMethod { int n, m, summ; int[] whogive, whoget, minData, U, V; int[,] delta; bool optimSolution = false; bool[,] elemChecked; List maxDelta = new List(); Element[,] mainData; MinDistrib md; /// /// Инициализирует новый экземпляр класса PotentialMethod с чтением исходных данных по указанному пути, задавая начальные значение внутренним переменным /// /// Путь к файлу с исходными данными public PotentialMethod(string path) { List newdata = ReadSaveData.ReadData(path); n = newdata.Count - 1; m = newdata.First().Length - 1; whogive = new int[n]; whoget = new int[m]; V = new int[n]; U = new int[m]; elemChecked = new bool[n, m]; mainData = new Element[n, m]; for (int i = 0; i < newdata.Count; i++) { for (int j = 0; j < newdata.First().Length; j++) { if (i != 0) { whogive[i - 1] = Convert.ToInt32(newdata[i][0]); if (j != 0) { whoget[j - 1] = Convert.ToInt32(newdata[0][j]); mainData[i - 1, j - 1].Value = Convert.ToInt32(newdata[i][j]); } } } } } /// /// Осуществляет проверку на вырожденность /// /// Экземпляр класса MinDistrib, выполняющий распределение по мин. элементу void CheckVir(MinDistrib mg) { if (mg.CountNotNullElement != m + n - 1) { FindMin(); while (true) { if (mainData[minData[1], minData[2]].Delivery == 0) { mainData[minData[1], minData[2]].Delivery = -1; mg.CountNotNullElement++; break; } else FindMin(); } } } /// /// Находит оптимальное решение транспортной задачи /// public void MainSolution() { //выполняется распределение по минимальному элементу md = new MinDistrib(mainData, whogive, whoget, m, n); //проверка на вырожденность, где добавляется фиктивная поставка while (md.MinDistribute(ref mainData) != m + n - 1) { CheckVir(md); } while (optimSolution == false) { // заполняет изначально массивы с потенциалами, чтобы потом перезаписать на нормальные V = new int[n]; for (int i = 0; i < V.Length; i++) { V[i] = 99999; } U = new int[m]; for (int i = 0; i < U.Length; i++) { U[i] = 99999; } //нахождение макс. затрат в заполненых ячейках, чтобы были хоть какие-то значения для подсчета потенциалов double MaxCost = 0; int maxV = 0; int maxU = 0; for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { if (mainData[i, j].Delivery != 0 && mainData[i, j].Value > MaxCost) { MaxCost = mainData[i, j].Value; maxV = i; maxU = j; } } } //расчет потенциалов V[maxV] = 0; U[maxU] = mainData[maxV, maxU].Value - V[maxV]; for (int sania = 0; sania < m; sania++) // саню не трогать!!! for (int i = 0; i < n; i++) for (int j = 0; j < m; j++) if (mainData[i, j].Delivery != 0 && (V[i] == 99999 || U[j] == 99999)) //проверяется, является ли поставка пустой и не записаны ли для этой ячейки нормальные потенциалы { if (V[i] == 99999 && U[j] == 99999) //если обе такие, считать нечего continue; if (V[i] != 99999) { for (int k = 0; k < m; k++) if (mainData[i, k].Delivery != 0) U[k] = mainData[i, k].Value - V[i]; } if (U[j] != 99999) for (int k = 0; k < n; k++) if (mainData[k, j].Delivery != 0) V[k] = mainData[k, j].Value - U[j]; } //рассчет дельты (V[i] + U[j] - C[i,j]) и занесения максимальной в соответсвующий список //список очищается для выполнения алгоритма в цикле maxDelta.Clear(); maxDelta.Add(new int[] { 0, 0, 0 }); delta = new int[n, m]; for (int i = 0; i < n; i++) for (int j = 0; j < m; j++) if (mainData[i, j].Delivery == 0) { delta[i, j] = U[j] + V[i] - mainData[i, j].Value; if (delta[i, j] > maxDelta[0][0]) { maxDelta.RemoveAt(0); maxDelta.Add(new int[] { delta[i, j], i, j }); } } //проверка оптимальности метода //если есть хоть одна положительная дельта, то решение не оптимально for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { if (delta[i, j] > 0) { optimSolution = false; break; } else optimSolution = true; } if (optimSolution == false) break; } if (optimSolution == false) { double MaxCostInRowWithDelta = 0; int MaxCostInRowWithDeltaI = 0, MaxCostInRowWithDeltaJ = 0; //находим ячейку с поставкой и максимальными затратами в строке с макс. дельта //maxDelta[0][1] - возвращает индекс строки максимальной дельты //maxDelta[0][2] - возвращает индекс столбца максимальной дельты for (int j = 0; j < m; j++) { if (mainData[maxDelta[0][1], j].Delivery != 0 && mainData[maxDelta[0][1], j].Value > MaxCostInRowWithDelta) { MaxCostInRowWithDelta = mainData[maxDelta[0][1], j].Value; MaxCostInRowWithDeltaI = maxDelta[0][1]; MaxCostInRowWithDeltaJ = j; } } //находим ячейку с поставкой и максимальными затратами в столбце с макс. дельта double MaxCostInCOLUMNWithDelta = 0; int MaxCostInColumnWithDeltaI = 0, MaxCostInColumnWithDeltaJ = 0; for (int i = 0; i < n; i++) { if (mainData[i, maxDelta[0][2]].Delivery != 0 && mainData[i, maxDelta[0][2]].Value > MaxCostInCOLUMNWithDelta) { MaxCostInCOLUMNWithDelta = mainData[i, maxDelta[0][2]].Value; MaxCostInColumnWithDeltaI = i; MaxCostInColumnWithDeltaJ = maxDelta[0][2]; } } //находим, сколько товара мы можем переместить // сравнивается ячейка с поставкой и макс. затратами в столбце и в строке с макс. дельта // если какая-то из них больше, он берет меньшую double MaxAmountWeCanAfford; if (mainData[MaxCostInColumnWithDeltaI, MaxCostInColumnWithDeltaJ].Delivery > mainData[MaxCostInRowWithDeltaI, MaxCostInRowWithDeltaJ].Delivery) { MaxAmountWeCanAfford = mainData[MaxCostInRowWithDeltaI, MaxCostInRowWithDeltaJ].Delivery; } else { MaxAmountWeCanAfford = mainData[MaxCostInColumnWithDeltaI, MaxCostInColumnWithDeltaJ].Delivery; } //перемещаем товар //эта ячейка - поставка с макс. затратами в строке с макс. дельта, из неё вычитают перемещенный поставку mainData[MaxCostInRowWithDeltaI, MaxCostInRowWithDeltaJ].Delivery -= (int)MaxAmountWeCanAfford; //эта ячейка - сама макс. дельта, в неё перемещают поставку mainData[maxDelta[0][1], maxDelta[0][2]].Delivery += (int)MaxAmountWeCanAfford; //эта ячейка - поставка с макс. затратами в столбце с макс. дельта, из неё вычитают перемещенный поставку mainData[MaxCostInColumnWithDeltaI, MaxCostInColumnWithDeltaJ].Delivery -= (int)MaxAmountWeCanAfford; //эта ячейка сод-ит индекс стр. из ячейки с макс. зат-ми в ст-бце с макс.д. и индекс ст-бца из ячейки с макс. зат-ми в стр-ке с макс.д. //её заполняют как противолежащую максимальной дельте, таким образом образуя прямоугольник или квадрат mainData[MaxCostInColumnWithDeltaI, MaxCostInRowWithDeltaJ].Delivery += (int)MaxAmountWeCanAfford; } //рассчет суммы, если решение оптимально else { for (int i = 0; i < n; i++) for (int j = 0; j < m; j++) summ += mainData[i, j].Delivery * mainData[i, j].Value; break; } } //подготовка результатов для записи в файл List message = new List(); for (int i = 0; i < n; i++) { string[] row = new string[m]; for (int j = 0; j < m; j++) row[j] = (mainData[i, j].Value + "/" + mainData[i, j].Delivery); message.Add(row); } message.Add(new string[] {"Оптимальная стоимость", summ.ToString() }); ReadSaveData.WriteToFile("TransportSolution.csv", message); } /// /// Находит минимальный элемент в свойстве "затраты на перевозку" основной матрицы /// void FindMin() { int min = mainData[0, 0].Value; minData = new int[3]; for (int i = 0; i < n; i++) for (int j = 0; j < m; j++) if (min > mainData[i, j].Value && mainData[i, j].Value != 0 && min != 0 && mainData[i, j].Delivery == 0 && elemChecked[i, j] == false) { min = mainData[i, j].Value; minData[0] = min; minData[1] = i; minData[2] = j; } } } }