Ответы пользователя по тегу Алгоритмы
  • Какие методы Вы используете, чтобы быстро найти участок в проекте с сотней тысяч строк?

    Mrrl
    @Mrrl
    Заводчик кардиганов
    Go to definition и Find all references в Visual Studio. Реже - поиск имени функции или строки по всему solution.
    Ответ написан
    Комментировать
  • Как улучшить скорость функции?

    Mrrl
    @Mrrl
    Заводчик кардиганов
    Записать условие в виде (a+1)*(b+1) == n*(n+1)/2+1 = N, и для чисел a от (n+1)/2 до n проверить, делится ли N на a+1. Если делится, то b=N/(a+1)-1. На всякий случай, проверить, что b < n+1.
    При желании, перебор можно сократить примерно в 12 раз. Но тогда вместо проверки делимости надо будет быстро проверять, является ли число полным квадратом - что может быть не очень просто.
    Ответ написан
    2 комментария
  • Как работает данный алгоритм?

    Mrrl
    @Mrrl
    Заводчик кардиганов
    Страшноватый алгоритм...
    Сначала создаём строку, содержащую всю информацию из списка (то есть, нас вообще не волнует, что где-то была структура "список" - мы его убиваем, чтобы потом построить заново из сырых данных)...
    Потом превращаем строку в массив из строк! С помощью Split, разделяя её по пробелам!!! Зачем? Почему сразу не заполнить массив строк? А если строки в списке будут сами содержать пробелы, как "Новый список:"?
    Потом разворачиваем этот массив! Зачем? А потому, что нас не научили вставлять элементы в конец списка, мы можем вставить их только в начало. Проходить массив в обратном порядке, вероятно, тоже не умеем.
    Потом, уже при построении списка, у нас вызывается загадочная функция addNode(). Что она делает - неизвестно, поскольку, если бы она вставляла новый элемент после current - её бы вызвали ещё в двух местах кода, а не писали бы её код в развёрнутом виде.
    И, наконец, независимо от того, что делает addNode(), элемент tt[n+1] будет вставлен в список дважды.
    static Node SwapFirst(Node c) {
        if(c==null || c.next==null) return c;
        Node result=c.next;
        c.next=result.next;
        result.next=c;
        return result;
    }
    public static Node reverseMyList (Node c,int n) {
       if(n==0) return SwapFirst(c);
       Node d=c;
       for(;n>1 && d.next!=null;n--) d=d.next;
       if(n==1) d.next=SwapFirst(d.next);
       return c;
    }
    Ответ написан
    Комментировать
  • Как определить винрейт?

    Mrrl
    @Mrrl
    Заводчик кардиганов
    Никак. Возможно, результаты Б против С хуже потому, что Б готовилась к игре против А. А А - наоборот, к игре против С. Тогда может получиться, что А будет всегда проигрывать Б. А может быть и наоборот - когда А знает слабое место Б, которого не знает С. Тогда А будет выигрывать, возможно, даже чаще, чем в 80% случаев.
    Чтобы можно было что-то сказать, нужна модель - по возможности, однопараметрическая.
    Ответ написан
    7 комментариев
  • Как расписать циклы через goto, а потом преобразовать в state-машину?

    Mrrl
    @Mrrl
    Заводчик кардиганов
    Возьмём программу, в которой есть и циклы, и goto. Например, проверка, есть ли в матрице нулевая строка:
    void Check(int M[5][5]){
      for(int a=0;a<5;a++){
        for(int b=0;b<5;b++){
          if(M[a][b]!=0) goto L1;
       }
       goto L2;
    L1: continue;
      }
      printf("No\n"); return;
    L2:
      printf("Yes\n"); 
    }

    Опишем все переменные в начале, а циклы распишем прямо по определению.
    Кроме того, return превратим в goto на метку Return в конце функции:
    void Check(int M[5][5]){
      int a,b;
      a=0;
      goto Loop1_Check;
    Loop1_Body:
      b=0;
      goto Loop2_Check;
    Loop2_Body:
          if(M[a][b]!=0) goto L1;
          b++;
    Loop2_Check:
          if(b<5) goto Loop2_Body;  
       goto L2;
    L1: 
    Loop1_End:
       a++;
    Loop1_Check:  
      if(a<5) goto Loop1_Body;
      printf("No\n"); goto Return;
    L2:
      printf("Yes\n"); 
    Return: ;
    }

    Теперь между любыми двумя метками у нас находится линейная последовательность, в которой могут встретиться конструкции if(condition) goto label;
    Заводим переменную state.
    Каждой метке присваиваем какое-нибудь значение этой переменной, и вместо goto label пишем state=label; а вместо if(condition) goto label; - if(condition) state=label; else{ остаток кода до следующей метки }. Если перед какой-нибудь меткой label не было безусловного goto, пишем перед ней state=label;
    void Check(int M[5][5]){
      const int Return=0;
      const int Loop1_Check=1;
      const int Loop1_Body=2;
      const int Loop2_Check=3;
      const int Loop2_Body=4;
      const int L1=5;
      const int L2=6;
    
      int state;
      int a,b;
      a=0;
      state=Loop1_Check;
    Loop1_Body:
      b=0;
     state=Loop2_Check;
    Loop2_Body:
          if(M[a][b]!=0) state=L1;
          else{
             b++;
             state=Loop2_Check;
          }
    Loop2_Check:
          if(b<5) state=Loop2_Body;  
          else state=L2;
    L1: 
          a++;
          state=Loop1_Check;
    Loop1_Check:  
      if(a<5) state=Loop1_Body;
      else{
          printf("No\n"); 
          state=Return;
      }
    L2:
      printf("Yes\n"); 
      state=Return;
    Return: ;
    }

    (это преобразование было неэквивалентным).
    Теперь перед первой меткой вставляем while(state!=Return){ switch(state){
    а каждую метку label: заменяем на break; case label:
    break; перед первой меткой убираем, а case Return: меняем на } }
    void Check(int M[5][5]){
      const int Return=0;
      const int Loop1_Check=1;
      const int Loop1_Body=2;
      const int Loop2_Check=3;
      const int Loop2_Body=4;
      const int L1=5;
      const int L2=6;
    
      int state;
      int a,b;
      a=0;
      state=Loop1_Check;
      while(state!=Return){
        switch(state){
          case Loop1_Body:
            b=0;
            state=Loop2_Check;
            break;
          case Loop2_Body:
            if(M[a][b]!=0) state=L1;
            else{
              b++;
              state=Loop2_Check;
            }
            break;
          case Loop2_Check:
            if(b<5) state=Loop2_Body;  
            else state=L2;
            break;
          case L1: 
            a++;
            state=Loop1_Check;
            break;
          case Loop1_Check:  
             if(a<5) state=Loop1_Body;
             else{
                printf("No\n"); 
                state=Return;
             }
             break;
           case L2:
              printf("Yes\n"); 
              state=Return;
              break;
         }
      }
      ;
    }

    Всё.
    Ответ написан
    4 комментария
  • Как использовать метод Гаусса для итерационного метода Ньютона?

    Mrrl
    @Mrrl
    Заводчик кардиганов
    Итак, у вас есть система: F(X)=0. В точке X=(x1,x2,...,xn) её матрица Якоби равна J, а значение F(X)=Y. Шаг метода Ньютона с обратной матрицей выглядел бы как
    X'=X-J-1Y.
    Обозначим dX=J-1Y. Тогда J*dX=Y. А это - линейная система из n уравнений. Её действительно проще решить методом Гаусса, чем искать обратную матрицу и умножать на вектор. Так что находите dX и вычитаете из X.
    Ответ написан
    Комментировать
  • Как можно порождать перестановки с дополнительными ограничениями для элементов итеративно?

    Mrrl
    @Mrrl
    Заводчик кардиганов
    А чем не нравится goto? Вполне легальный оператор. В случае чего, от него легко избавиться, преобразовав код в конечный автомат (получится цикл с одним switch внутри). Хотя чаще хватает одной дополнительной логической переменной.
    И какого типа ограничения? И есть ли ограничение на порядок выдачи перестановок?

    UPD. Вот вариант решения на C#. Не самый эффективный, правда.
    class Program{
            static IEnumerable<int[]> Permutations(int N,Func<int,int,bool>Cond1,Func<int,int,int,int,bool> Cond2) {
                int[] res=new int[N];
                int k=0;
                res[0]=0;
                while(k>=0){
                    if(++res[k]<=N){
                        if(Cond1(k+1,res[k]) && GoodValue(res,k,Cond2)){
                            if(k==N-1) yield return res;
                            else res[++k]=0;
                        }
                    }else k--;
                }
            }
            static private bool GoodValue(int[] res,int k,Func<int,int,int,int,bool> Cond) {
                for(int i=0;i<k;i++) {
                    if(res[i]==res[k] || !Cond(i+1,res[i],k+1,res[k])) return false;
                }
                return true;
            }
    
            static int N=6;
            static bool Cond1(int a,int va) {
                if(a==1) return va==1;
                if(a==N) return va==N;
                return true;
            }
            static bool Cond2(int a,int va,int b,int vb) { 
                return Math.Abs(a-b)>1 || Math.Abs(va-vb)<=3;
            }
            static void Main(string[] args) {
                foreach(int[] perm in Permutations(N,Cond1,Cond2))
                    Console.WriteLine(String.Join(",",perm));
            }
        }

    Функции Permutations передаются два условия. Первое Cond1(a,va) проверяет, может ли элемент va находиться в позиции a, второе Cond2(a,va,b,vb) - могут ли одновременно находиться va в позиции a и vb в позиции b.
    В качестве примера - печать перестановок из задачи про лягушку из ProjectEuler: https://projecteuler.net/problem=490
    Для N=6 выдаёт ожидаемые 14 перестановок:
    1,2,3,4,5,6
    1,2,3,5,4,6
    1,2,4,3,5,6
    1,2,4,5,3,6
    1,2,5,3,4,6
    1,2,5,4,3,6
    1,3,2,4,5,6
    1,3,2,5,4,6
    1,3,4,2,5,6
    1,3,5,2,4,6
    1,4,2,3,5,6
    1,4,2,5,3,6
    1,4,3,2,5,6
    1,4,5,2,3,6


    Если не нравится конструкция yield return - вставьте вместо неё свою обработку перестановки, а функцию опишите, как void. Если из GoodValue yбрать проверку res[I]==res[k], то вместо перестановок будут печататься все N-элементные наборы из чисел 1..N, удовлетворяющие условию.
    Ответ написан
    4 комментария
  • Пример реализации односвязного списка на C#, Не понятно откуда берется свойство Netx Объекта Head?

    Mrrl
    @Mrrl
    Заводчик кардиганов
    Когда список пуст, Current и Head равны null. После добавления первого элемента Current и Head являются одним и тем же объектом, а при добавлении второго срабатывает строчка Current.Next = node; - и в этот момент устанавливается Head.Next (поскольку Current и Head совпадают).
    Ответ написан
    5 комментариев
  • Как сгенерировать окружности на сфере, что бы они оптимально перекрывали друг-друга?

    Mrrl
    @Mrrl
    Заводчик кардиганов
    Это известная проблема. Пока её решили до 10 кругов и ещё для некоторых чётных значений. Впрочем, всё это сведения 25-летней давности, свежих результатов найти не удаётся. Вот статья, где разбирается построение для 11 кругов (правда, я её не читал, и не знаю, есть ли там окончательное решение для этого случая):
    https://eudml.org/doc/141375
    Или у вас задача - "дан радиус сферы и радиус круга, найти минимальное число кругов"? Или можно задать примерный радиус (или примерное число кругов) и найти какое-нибудь решение, близкое к оптимальному, для этого случая?
    В общем, сейчас задача упирается в вопрос "а зачем?". Если нужно точное решение для написания докторской диссертации - то решайте. Если есть какая-нибудь практическая цель - напишите, какая, и будем искать реалистичные приближения.

    UPD. В общем, берёте икосаэдр, и каждую грань делите на маленькие правильные треугольники. Вершины этих треугольников и дадут центры кругов. Радиус придётся считать исходя из картинки вблизи вершины икосаэдра - там треугольники заметно искажаются. Возможно, радиальное расстояние между точками там придётся слегка уменьшить.
    Ответ написан
    7 комментариев
  • Почему не работает мой mergesort?

    Mrrl
    @Mrrl
    Заводчик кардиганов
    Видны две ошибки. Первая не должна влиять на результат, но может привести к выходу индекса за границу массива:
    if ((i < leftLen && left[i] < right[j]) || j >= rightLen) {

    В случае, когда j==rightLen, у вас будет проверяться элемент right[j]. Условия здесь надо поменять местами:
    if ( j >= rightLen || (i < leftLen && left[i] < right[j])) {

    Вторая - что элемент array[mid] не будет участвовать ни в первом, ни во втором рекурсивных вызовах mergeSort. Второй вызов надо было сделать
    mergeSort(array, mid, end);
    А чтобы не было бесконечной рекурсии, изменить условие в mergeSort на
    if (start < end-1) {
    (или на if(start < mid) ).

    Ну, и в классическом С невозможна конструкция

    int leftLen = mid - start,
          left[leftLen];

    Там массивы могут быть только фиксированной длины, а для динамического захвата приходится использовать malloc. Но если компилируется, значит, ваша версия компилятора такое позволяет.

    UPD. В С99 действительно возможны массивы переменной длины, и пишут, что gcc их поддерживает. Ну и ладно. Visual Studio такое не позволяет.
    Ответ написан
    1 комментарий
  • Перебор массива циклом или сверка значений проверкой?

    Mrrl
    @Mrrl
    Заводчик кардиганов
    На С++ прямая проверка работает примерно вдвое быстрее, чем проверка циклом. Я проверял для массива из 10 элементов (проверял, входит ли число в первые 10 простых чисел). С циклом проблема ещё и в том, что для такого "if" трудно написать else-часть - нужно либо добавлять лишнюю логическую переменную, либо использовать goto.
    Ответ написан
    Комментировать
  • Можно ли одной bitwise операцией (без циклического сдвига) определить степень двойки(номер бита)?

    Mrrl
    @Mrrl
    Заводчик кардиганов
    Можно взять остаток от деления на 37, и дальше по табличке. Но это может оказаться дороже, чем цикл.
    Или за 5 операций AND:

    int n=0;
    if(x&0xffff0000) n+=16;
    if(x&0xff00ff00) n+=8;
    if(x&0xf0f0f0f0) n+=4;
    if(x&0xcccccccc) n+=2;
    if(x&0xaaaaaaaa) n++;

    Хотя через CLZ лучше.
    Ответ написан
    Комментировать
  • Как эффективней рисовать в 2d на C#?

    Mrrl
    @Mrrl
    Заводчик кардиганов
    Как насчёт Bitmap.LockBits()? Оно требует знания формата bitmap, но является самым быстрым способом редактирования.
    Ответ написан
  • PHP: как сгруппировать близкие по значению элементы массива?

    Mrrl
    @Mrrl
    Заводчик кардиганов
    Непонятная задача.
    Во-первых, решений может быть много. Например, пусть границы - abs(dx) <= 5, abs(dy) <= 5. Точки A=(0,0), B=(4,4), C=(7,7). Тогда решением будет и {A,B},{C} и {A},{B,C}. Из них правильно какое-то одно, или оба?
    А если точки A=(0,0), B=(4,4), C=(7,7), D=(11,11)? Годится ли решение {A},{B,C},{D}? В нём больше групп, чем в {A,B},{C,D}, но центральная группа плотнее.
    И наконец, является ли условие "posX && posY в группе отличаются каждая друг от друга не более чем на +\- какое-то значение" единственным? Если да - достаточно создать N групп, по одному элементу в каждой. Условие будет выполнено. А если нет - попытайтесь объяснить, почему конкретно это решение не годится. Сформулировать условие, которое нарушено. Может быть, тогда станет понятнее, что же это за задача. А пока это лишь пожелание "сделайте мне красиво".

    А если задача не формализована - ищите слово "кластеризация". Про неё много интересного написано.
    Ответ написан
    2 комментария
  • Алгоритм поиска точки, максимально удалённой от границ фигуры на изображении?

    Mrrl
    @Mrrl
    Заводчик кардиганов
    Рассматриваем фигуру как граф: у каждой точки соседями считаются точки из небольшой окрестности (например, 7*7), а длиной ребра - евклидовы расстояния между точками. Тогда расстояние между двумя точками фигуры с хорошей точностью равно кратчайшему пути на этом графе. Осталось найти точку, самую далёкую от границы. Считается за O(S*log(S)), где S - площадь фигуры.
    Ответ написан
  • Как найти кратчайший путь в лабиринте?

    Mrrl
    @Mrrl
    Заводчик кардиганов
    Заведите ещё один массив и кладите в него направление, откуда пришли в данную клетку. Проще всего это направление передать параметром в FindPath. Потом пройдите по этим направлениям от финиша до старта.
    Другой вариант - на обратном проходе искать соседнюю клетку со значением tempMap на 1 меньшим, чем в данной клетке, и идти в неё. Это дольше писать и оно чуть медленнее работает, но требует меньше памяти.
    Лабиринт действительно на сетке из шестиугольников?
    Ответ написан
  • Как строятся графики по набору данных?

    Mrrl
    @Mrrl
    Заводчик кардиганов
    Я обычно делаю так. Предположим, что желаемая форма графика известна - прямая, парабола, синусоида, поверхность второго порядка... Ищу коэффициенты наилучшего приближения по методу наименьших квадратов. Смотрю для каждой точки величину остаточной ошибки, считаю средний квадрат ошибок (сигму). Отбрасываю все точки, для которых ошибка больше 2.5*сигма, для остальных ищу график снова. И так, пока процесс не стабилизируется.
    А вообще надо подробнее написать, что за данные, откуда они взялись, в каком диапазоне, что нужно от графика - например, должен ли он строго проходить через точки, или должен проходить в области наибольшего сгущения, известно ли что-нибудь про зависимость заранее... А так задача слишком общая.
    Ответ написан
    1 комментарий
  • Как решать задачу на счастливые билеты?

    Mrrl
    @Mrrl
    Заводчик кардиганов
    Как-то так:

    int S0[37],S1[37],S2[37],S3[37],S4[37];
    
    void Expand(int *a,int *b,int n){
    	int s0=0;
    	for(int i=0;i<n+10;i++){
    		if(i<n) s0+=a[i];
    		if(i>=10) s0-=a[i-10];
    		b[i]=s0;
    	}
    }
    
    int Conv(int *S,int balance,int m){
    	int res=0;
    	while(--m>=0) res+=S[m]*S4[balance+m];
    	return res;
    }
    
    // number of tickets in 0..u-1
    int NHappy(int u){
    	int res=0;
    	int balance=0;
    	int digit;
    
    	digit=u/10000000; u%=10000000;
    	for(int i=0;i<digit;i++) res+=Conv(S3,balance++,28);
    	digit=u/1000000; u%=1000000;
    	for(int i=0;i<digit;i++) res+=Conv(S2,balance++,19);
    	digit=u/100000; u%=100000;
    	for(int i=0;i<digit;i++) res+=Conv(S1,balance++,10);
    	digit=u/10000; u%=10000;
    	for(int i=0;i<digit;i++) res+=S4[balance++];
    	digit=u/1000; u%=1000;
    	for(int i=0;i<digit && balance>=0;i++) res+=S3[balance--];
    	digit=u/100; u%=100;
    	for(int i=0;i<digit && balance>=0;i++) res+=S2[balance--];
    	digit=u/10; u%=10;
    	for(int i=0;i<digit && balance>=0;i++) res+=S1[balance--];
    	if(balance>=0 && balance<u) res++;
    	return res;
    }
    
    void __cdecl main(){
    	S0[0]=1;
    	Expand(S0,S1,10);
    	Expand(S1,S2,19);
    	Expand(S2,S3,28);
    	Expand(S3,S4,37);
    
    	int m,n;
    	for(;;){
    		printf("m,n: ");
    		scanf("%d%d",&m,&n);
    		printf("Result: %d\n",NHappy(99999999)+1-NHappy(m)-NHappy(99999999-n));
    	}
    }

    Проверок входных данных не делается. На простых тестах результаты были правильными. Работает, по моим оценкам, быстрее, чем за 2000 операций. Если нужно проверять много диапазонов, то можно предварительно вычислить частичные суммы Conv() в циклах, и тогда любой диапазон сосчитается за два десятка операций (и при этом не будет требовать гигабайта памяти).

    UPD: Вот оптимизированный вариант:
    int S[8][38];
    
    void InitS(){
    	for(int i=1;i<38;i++) S[0][i]=1;
    	for(int a=1;a<9;a++){
    		int s0=0;
    		for(int i=1;i<=37;i++){
    			s0+=S[a-1][i];
    			if(i>10) s0-=S[a-1][i-10];
    			S[a][i]=s0;
    		}
    	}
    }
    
    // number of tickets in 0..u-1
    int NHappy(int u){
    	int res=0;
    	int balance=36;
    	for(int a=7,b=10000000;a>=0;a--,b/=10){
    		int digit=u/b;
    		u%=b;
    		int b1;
    		if(a>3){
    			balance-=9;
    			res+=S[a][b1=balance+digit]-S[a][balance];
    		} else{
    			b1=balance-digit; if(b1<0) b1=-1;
    			res+=S[a][balance+1]-S[a][b1+1];
    		}
    		balance=b1;
    	}
    	return res;
    }
    
    void __cdecl main(){
    	InitS();
    	int m,n;
    	for(;;){
    		printf("m,n: ");
    		scanf("%d%d",&m,&n);
    		printf("Result: %d\n",NHappy(99999999)+1-NHappy(m)-NHappy(99999999-n));
    	}
    }

    Не спрашивайте, почему :)
    Ответ написан
    3 комментария
  • Является ли наличие двух одинаковых констант в коде признаком не оптимальности решения?

    Mrrl
    @Mrrl
    Заводчик кардиганов
    Зависит от длины константы. Что короче - загрузить её в регистр непосредственно в команде, или взять из памяти (указав в команде адрес)? Сильно зависит от процессора.
    В целом, одинаковые константы в алгоритме это не страшно. Хуже, когда одно и то же условие приходится проверять много раз. Или, что ещё страшнее, условия, следующие одно из другого (проверили, что a < b, а потом проверяем, что a+c < b+c). Такое часто встречается в геометрии. И в этом случае над алгоритмом стоит поработать.
    Ответ написан
    Комментировать
  • Какие ресурсы "экономить" при алгоритмизации?

    Mrrl
    @Mrrl
    Заводчик кардиганов
    Если вы хотите экономить память, то проблема будет не в переменных, а в больших структурах (списки, массивы, словари...). Мне чаще всего встречаются такие ситуации:
    - память в обмен на время. Запоминать результаты или вычислять заново? Иногда здесь удаётся обменять экспоненциальное время на полиномиальное, или уменьшить степень полинома на 1 или на 2. Но чаще выигрыш идёт только в константу раз (хотя в узком месте это может быть немаловажно). Впрочем, иногда вычислить функцию оказывается быстрее, чем найти её значение в таблице - так что надо такие оптимизации проверять.
    - замена обработки большого массива в памяти на послойную обработку (когда очередной фрагмент/строка/двумерный слой трёхмерного массива... вычисляется, обрабатывается, а уже ненужный слой забывается). Здесь память экономится за счёт серьёзного усложнения кода - приходится писать всякие конвейеры. Зато появляется шанс сэкономить ещё и время с помощью параллельной обработки.
    - хранение данных, которые должны быть в памяти, в упакованном виде. Трудно читать, очень трудно писать, код усложняется, время теряется. Но появляется шанс уложиться в 3 ГБ и остаться в 32-битном режиме (если это важно).
    - хранение в памяти против чтения из файла. Либо будет теряться время, либо усложняться код (если сумеете воспользоваться асинхронным чтением/записью).
    - в тяжелых случаях - экономия байтов кода (это если пишете на ассемблере). Может пригодиться, если действительно пишете сервер для кофеварки. Но возможно, здесь лучше попытаться поставить процессор помощнее.

    Сначала пишите, как проще, но если есть риск развития проекта за пределы текущих ресурсов, предусмотрите возможность постепенного перехода на более экономные по памяти (или, там где надо - по времени) алгоритмы.
    Ответ написан
    2 комментария