Сочетания. Реализация. Вопросы и предложения

SashaMercury
Дата: 28.11.2014 06:40:37
Здравствуйте.
Решил создать новую тему, ибо мне кажется данную задачу можно решить разными способами, и акцент в данном случае не идёт на длинную арифметику. Проблема была в том, что мы хотели сократить знаменатель к единице. Если администрация считает что не стоит создавать новый топик, то не возражаю чтобы, его закрыли.
То что требуется я реализовал сегодня.

Вспомогательные моменты.



struct Number
{
	int num;
	int num2;
	int count;
	int* multipliers;
};

struct Number* initializeNumber(int a)
{
	struct Number* n = (struct Number*)malloc(sizeof(struct Number));
	n->num = a;
	n->num2 = a;
	n->count = 0;
	n->multipliers = NULL;
}

int viewNumber(struct Number* a)
{
	printf("num= %i\n", a->num);
	printf("num2= %i\n", a->num2);
	printf("divisors:");
	for (int i = 0; i < a->count; ++i)
	{
		printf("%i ", a->multipliers[i]);
	}
	printf("\n");
	return 0;
}

//assume a->num > 1
int factorization(struct Number* a)
{
	for (int i = 2; i<=a->num2; ++i) 
	{
		if (a->num2%i == 0)
		{
			a->count += 1;
			a->multipliers = (int*)realloc(a->multipliers, a->count*sizeof(int));
			a->multipliers[a->count - 1] = i;
			
			a->num2 /= i;
			factorization(a);
			break;
		}
	}
	return 0;
}


Как работает factorization. Например на входе 150, находит первый делитель слева 2, дальше рекурсивный запуск 75, находим 3, дальше рекурсивный запус 25, находим 5, дальше рекурсивный запуск 5, находим 5, рекурсивный запуск на 1, приводит к завершению. Вместо 150 получаем массив 2 3 5 5 .
Возможно данную функцию можно реализовать лучше, буду рад предложениям.

Вы наверное догадались зачем нужна эта функция. Фиксируем элемент знаменателя, и пытаемся найти такой элемент числитель, что a%b==0,если такой элемент не найден, то фиксированный элемент знаменателя раскладываем на простейшие и расширяем массив знаменателя . Вместо 150 у нас появится 4 числа 2 3 5 5 . Ниже код


//Сочетания
char* la_combination(unsigned n, unsigned m)
{
	m = (n - m > m) ? m : n-m;
	int* up = (int*)malloc(sizeof(int)*m);//числитель
	int* down = (int*)malloc(sizeof(int)*m);//знаменатель

	for (int i = 1; i <= m; ++i)
	{
		*(up + m - i) = n - m + i;
		*(down + m - i) = i;
	}
#ifdef DEBUG
	for (int i = 0; i < m; ++i)
		printf("%i ", up[i]);
	printf("\n");
	for (int i = 0; i < m; ++i)
		printf("%i ", down[i]);
	printf("\n");
#endif

	int len_down = m;
	for (int i = 0; i < len_down; ++i)//1 проход по знаменателю, длина знаменателя меняется
	{
		if (down[i] != 1)
		{
			int j = 0;
			for (j = 0; j < m; ++j)//проход по числителю
			{
				if (!(up[j] % down[i]))
				{
					up[j] /= down[i];
					down[i] = 1;
					break;
				}
			}
			if (j == m && down[i]!=1) //если перебрали все элементы числителя, а фиксированный элемент знаменателя не сократили
			{
				struct Number* n = initializeNumber(down[i]);
				factorization(n); //раскладываю число на множители
				down[i] = 1;
				len_down += n->count;
				down = (int*)realloc(down, sizeof(int)*len_down);
				for (int i = 0; i < n->count; ++i)
				{
					down[len_down - n->count + i] = n->multipliers[i];
				}
				free(n->multipliers);
				free(n);
			}
		}
	}
#ifdef DEBUG
	for (int i = 0; i < m; ++i)
		printf("%i ", up[i]);
	printf("\n");
	for (int i = 0; i < len_down; ++i)
		printf("%i ", down[i]);
	printf("\n");
#endif
	char* res="1";
	char* t = (char*)malloc(10);
	for (int i = 0; i < m; ++i)
	{
		sprintf(t, "%d", up[i]);
		res = la_multiplication(res, t);
	}
	free(t);
	return res;
}


На функцию la_multiplication(res, t) можно не обращать внимание (но если нужна она реализована в соседнем топе), это сложение двух строк.


Мне интересно как вы бы решили аналогичную задачу, ну и возможно у кого-то есть замечания по рефакторингу кода, оптимизации алгоритмов, и конечно-же организации данных. (Может стоит создать статическую переменную под длину массива, и не создавать структуру, например)


PS
Вчера с трудом заснул и снились кошмары(из-за того что меня одолевали сомнения по тернарному оператору). Всё-таки не стоит использовать С++ только из-за расширенных возможностей тернарного оператора, сегодня изменил весь код в длинной арифметике на код Си.
SashaMercury
Дата: 28.11.2014 13:11:54
Ребята, у меня сегодня был мой первый memory leak !!! Буквально 3 часа назад
SashaMercury
Дата: 28.11.2014 13:15:53
В том смысле, что во время выполнения программы, появилось сообщение Memory leak, и выполнение программы остановилось Первый раз !
mayton
Дата: 28.11.2014 13:18:36
Поздравляю. Прям как первый раз с женщиной....

А зачем тут рекурсия? Ну ... как-бы обычно ее используют там
где она удобна и наглядна. Деревья поиска там. Обход сложных
связных структур. А тут?
Anatoly Moskovsky
Дата: 28.11.2014 13:19:14
SashaMercury
мой первый memory leak

Ага, щас. Далеко уже не первый. Мы просто жалели вас, не сообщали горькую правду :)

ЗЫ. Ничего страшного. У половины человечества ежемесячно бывает leak, и ничего, живут
SashaMercury
Дата: 28.11.2014 13:22:11
Anatoly Moskovsky,

были раньше, но программа никогда не вылетала. А тут вылетела, и всё как надо, сообщение, мол у вас Memory leak, получите распишитесь !
SashaMercury
Дата: 28.11.2014 13:25:46
mayton, спасибо :)
Ну а как тут без рекурсии ? Интуитивно она больше всего подходит. Я ведь не все делители числа ищу, а раскладываю число на минимальные множители (по факту на произведение простых чисел)
mayton
Дата: 28.11.2014 14:55:46
for (int i = 2; i<=a->num2; ++i) 
	{
		if (a->num2%i == 0)

О некоторых граблях с топорами.

Я расскажу для НЕ-рекурсивного варианта. То как я кодил факторизацию сам.
А ты уж подумай как эту инфу переварить.

Во первых. Не нужно делить на чётные. Нужно деление на 2
вынести из тела цикла. И захардкодить.

А цикл делать начиная от 3 и с шагом в 2.

И верхняя граница должна быть не до числа N которое ты проверяешь
а до SQRT(N). До квадратного корня.

Для квадратного корня не нужно брать вещественный кастинг.
Есть эффективный расчёт для целочисленного корня.
Есть оптимизации на базе производной от этого-же корня.

Уже расчитанные primes нужно складывать в кеш и использовать
их при разложении других неизвестных составных чисел.

Кеш должен адаптивно расти и подстраиваться под диапазон
самых крупных факторизаций.

Кеш можно сжимать исходя из свойств строгой монотонности
хранимых значений. Монотонность имеет характер очень
малого роста.

Последние факты я установил экспериментально.

И последнее. Задачи с primes и факторизациями в прикладных
применениях имеют разрядную сетку во много раз превышающую
int. В задачах критографии например оперируют с составным
числом порядка 128/256/512 бит.
SashaMercury
Дата: 28.11.2014 15:10:15
Я расскажу для НЕ-рекурсивного варианта. То как я кодил факторизацию сам.
А ты уж подумай как эту инфу переварить.

mayton
Во первых. Не нужно делить на чётные. Нужно деление на 2
вынести из тела цикла. И захардкодить.

А цикл делать начиная от 3 и с шагом в 2.


Почему ? Какой я результат получу для числа 4, например ?

mayton
И верхняя граница должна быть не до числа N которое ты проверяешь
а до SQRT(N). До квадратного корня.


Если цикл начинать с 2, то видимо так. А если с трёх, то для числа 22 например, корень между 4 и 5, таким образом делителей я не найду, хотя по идее должен был встретить 11(Если пропустить 2).
mayton
Дата: 28.11.2014 15:38:46
Делителя 4 не должно существовать в принципе. Потому что делитель 4 это суть
дважды делитель по два.

Если ты где-то делишь на 4 значит скорее всего дефект в алгоритме факторизации.
Я говорю СКОРЕЕ ВСЕГО потому что не совсем представляю глубину твоего замысла.
С рекурсией и с глобальное переменной *Number. Мне не совсем ясен ее контракт.
Тоесть как с ней ПРАВИЛЬНО нужно работать.