В настоящее время я работаю над решением Вопроса о различных полномочиях (29) в проекте Эйлера. Проблема заключается в нахождении количества различных продуктов для (a^b), где (2 <a <100) и (2 <b <100). Изначально я создал алгоритм, который успешно обрабатывает факторизацию и проверяет наличие дубликатов. Однако, когда я пытался повысить производительность программы с помощью связанных списков и двоичного поиска, я столкнулся с проблемами, приводящими к дампу ядра. Несмотря на попытки выявить логические ошибки, мне не удалось решить проблему.
Вот фрагмент кода, с которым я работаю:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define A_MIN 2
#define A_MAX 100
#define B_MIN 2
#define B_MAX 100
struct Node {
int *data;
struct Node *next;
struct Node *previous;
};
void primeFactor(int *arr, int num) {
for (int k = 2; k <= num; ++k) {
while (num % k == 0) {
num /= k;
arr[k] += 1;
}
}
}
int compareArrays(const int *arr1, const int *arr2, int size) {
for (int k = 0; k < size; ++k) {
if (arr2[k] < arr1[k]) {
return -1; // arr2 is left of arr1
} else if (arr1[k] < arr2[k]) {
return 1; // arr2 is right of arr1
}
}
return 0; // arr2 is arr1
}
void insertSorted(struct Node **head, const int *arr, int size, int *listLength) {
struct Node *current = *head;
int pos = 0;
int left = 0;
int right = *listLength;
int comparison = -3;
while (current != NULL && left < right) {
int mid = (left + right) / 2;
while (pos < mid) {
current = current->next;
pos++;
}
while (pos > mid) {
current = current->previous;
pos--;
}
comparison = compareArrays(current->data, arr, size);
if (comparison == 1) { // arr2 is right of arr1
left = mid + 1;
} else if (comparison == -1) { // arr2 is left of arr1
right = mid;
} else {
break;
}
}
struct Node *prev = (current != NULL) ? current->previous : NULL;
struct Node *nxt = (current != NULL) ? current->next : NULL;
if (comparison != 0) {
struct Node *newNode = (struct Node *)malloc(sizeof(struct Node));
if (newNode == NULL) {
perror("Memory allocation error");
exit(EXIT_FAILURE);
}
newNode->data = (int *)malloc(size * sizeof(int));
if (newNode->data == NULL) {
perror("Memory allocation error");
exit(EXIT_FAILURE);
}
memcpy(newNode->data, arr, size * sizeof(int));
if (comparison == -1) { // perform insert left
newNode->next = current;
if (current != NULL) {
current->previous = newNode;
}
if (prev == NULL) {
*head = newNode;
} else {
prev->next = newNode;
newNode->previous = prev;
}
} else if (comparison == 1) { // perform insert right
newNode->next = nxt;
if (current != NULL) {
current->next = newNode;
}
if (nxt != NULL) {
nxt->previous = newNode;
}
} else { // no node exist yet. make node the head.
printf("lol");
*head = newNode;
}
(*listLength)++;
}
}
void freeLinkedList(struct Node *head) {
while (head != NULL) {
struct Node *temp = head;
head = head->next;
free(temp->data);
free(temp);
}
}
int main() {
struct Node *link_list = NULL;
int size = A_MAX;
int listLength = 0;
for (int i = A_MIN; i <= A_MAX; ++i) {
int arr[A_MAX] = {0};
primeFactor(arr, i);
for (int j = B_MIN; j <= B_MAX; ++j) {
int *arr2 = (int *)malloc(size * sizeof(int));
if (arr2 == NULL) {
perror("Memory allocation error");
exit(EXIT_FAILURE);
}
for (int k = 0; k < size; ++k) {
arr2[k] = arr[k] * j;
}
insertSorted(&link_list, arr2, size, &listLength);
}
}
printf("%d\n", listLength);
freeLinkedList(link_list);
return 0;
}```
Как насчет того, чтобы вообще не хранить факторизации? Должна быть возможность проанализировать каждый из них в свете связанных a и b, чтобы определить, следует ли его считать (для этого a, b). По сути, это упражнение по выбору канонической пары a, b для каждого результата. Я думаю, что вариант с максимальным радиусом действия a будет подходящим выбором.
Что касается программы, которую вы на самом деле написали, Valgrind — это полезный инструмент для обнаружения ошибок использования памяти при запуске программ. Сейчас самое время добавить его в свой пояс с инструментами. Вы получите максимальную информацию от valgrind, если запустите его в двоичном файле, содержащем символы отладки.
Ваш массив простых множителей должен быть достаточно большим, чтобы вместить самое большое простое число, с которым вы столкнетесь. Это означает, что его размер должен быть на единицу больше. Это работает, потому что A_MAX не является простым числом, но если бы это было так, то вы бы писали после конца массива, когда i в main является A_MAX. Например, если A_MAX равен 101, это сломается.
Использование связанного списка и его сортировка только увеличат временную сложность, поэтому я предлагаю вам использовать дерево двоичного поиска. PS: мой подход на c++





Один логический недостаток, который определенно может вызвать такие проблемы, как дамп ядра, связан со следующим разделом кода в вашей функции insertSorted:
memcpy(newNode->data, arr, size * sizeof(int));
if (comparison == -1) { // perform insert left
newNode->next = current;
if (current != NULL) {
current->previous = newNode;
}
if (prev == NULL) {
*head = newNode;
} else {
prev->next = newNode;
newNode->previous = prev;
}
} else if (comparison == 1) { // perform insert right
newNode->next = nxt;
if (current != NULL) {
current->next = newNode;
}
if (nxt != NULL) {
nxt->previous = newNode;
}
} else { // no node exist yet. make node the head.
printf("lol");
*head = newNode;
}
Код использует malloc для выделения памяти для newNode, но, как указано на странице руководства malloc,
Память не инициализируется.
Поскольку comparison может быть только 0, -1 или 1, и этот код находится внутри условного if (comparison != 0) {, то финальный else никогда не должен выполняться. Таким образом, значению newNode->next всегда присваивается допустимое значение. Однако для newNode->previous это не всегда так. Например, если comparison == -1, то newNode->previous устанавливается только в том случае, если prev != NULL.
Это означает, что newNode->previous может быть неинициализировано, поэтому его использование, например, со строкой current = current->previous; ранее в insertSorted, может привести к доступу к недопустимой памяти.
Эту проблему можно легко решить. Обратите внимание, что замена вашего вызова malloc на calloc , который «... инициализирует все байты в выделенном хранилище нулевыми значениями», является вариантом, но, как говорится в комментарии Нила,
Даже
callocне гарантирует инициализацию нулевых указателей; это работает на большинстве компьютеров, потому что null, скорее всего, равен 0 (5.13).
Таким образом, чтобы повысить надежность, лучше добавить строку newNode->previous = NULL; сразу после строки memcpy(newNode->data, arr, size * sizeof(int)); в указанном мной разделе вашего кода.
Кроме того, чтобы при случайном чтении кода было более понятно, что newNode->next всегда инициализируется, и чтобы последующие изменения кода не нарушили ваш код, я предлагаю также добавить строку newNode->next = NULL; изначально.
@Neil Спасибо за напоминание об этой проблеме. Таким образом, я обновил свой ответ, чтобы не рекомендовать полагаться на calloc, а также процитировал первое предложение вашего комментария.
Вы используете двоичный поиск в связанном списке, чтобы найти точку вставки для следующего узла. Бинарный поиск эффективен, когда у вас есть произвольный доступ к элементам, но гораздо менее эффективен, когда у вас его нет. Линейный поиск в вашем случае будет проще реализовать и, возможно, быстрее.
Даже если ваш связанный список не использует фиктивный головной узел, вы можете временно предоставить его для операций со связанным списком. Это могло бы обеспечить более чистый код, устранив необходимость специальной обработки пустого списка.
Вы используете неэффективное представление для простых факторизаций: массив показателей A_MAX. Однако ненулевые показатели степени будут только для простых чисел, поэтому вы можете легко обойтись гораздо более короткими массивами, где каждый элемент соответствует простому числу.
Во многих случаях вашей программе не удается инициализировать указатели previous своих динамически выделяемых узлов списка. В некоторых случаях не удается инициализировать указатель next. malloc() не инициализирует за вас выделенную память. calloc() инициализирует его нулевым байтом, но C не определяет, что это означает как представление указателя, поэтому даже с calloc() вам нужно будет явно присвоить значения членам-указателям выделенных вами узлов.
Вам вообще не нужно хранить или сравнивать факторизации, хотя я бы все равно их вычислил. Вы можете непосредственно определить по каждой факторизации ab, будет ли она также считаться как a'b', где a' < a:
вычислите наибольший общий знаменатель d(a) всех ненулевых показателей степени в простой факторизации числа a. Часто это будет 1.
Тогда существует целое положительное число a0 такое, что a0d(a) = a. Не существует меньшего натурального числа r такого, что a является целой степенью r.
ab, конечно, также является целой степенью a0: a0d(a)b.
Пусть x(a) = d(a)b. Вы избежите подсчета дубликатов, если будете считать только те ab, где b — наименьший делитель x(a), такой что x(a) / b <= bmax. Если это не так, существует некоторая степень a0 меньшая, чем a, для которой будет учитываться ab.
вот как выглядит мой код с предложениями Джона Омилана! Невозможность инициализировать атрибут узла является основной причиной дампа ядра.
void insertSorted(struct Node **head, const int *arr, int size, int *listLength) {
int comparison = -1;
// unchanged code goes here.
if (comparison != 0) {
// more unchanges code goes here.
if (comparison == -1) { // perform insert left
newNode->next = current;
if (current == NULL) {
newNode->next = NULL;
}else{
newNode->next = current;
current->previous = newNode;
}
if (prev == NULL) {
newNode->previous = NULL;
*head = newNode;
} else {
newNode->previous = prev;
prev->next = newNode;
}
} else if (comparison == 1) { // perform insert right
newNode->next = nxt;
if (current == NULL) {
newNode->previous = NULL;
*head = newNode;
}else{
newNode->previous = current;
current->next = newNode;
}
if (nxt == NULL) {
newNode->next = NULL;
}else{
newNode->next = nxt;
nxt->previous = newNode;
}
}
(*listLength)++;
}
}
Вам может понадобиться: en.m.wikipedia.org/wiki/Red%E2%80%93black_tree