#c #algorithm #sorting #merge
#c #алгоритм #сортировка #слияние
Вопрос:
Я пытаюсь реализовать алгоритм сортировки слиянием, который может работать с целыми числами, строками и символами. К сожалению, я обнаружил, что он не работает должным образом с целыми числами, когда длина массива нечетная.
Например: Ввод: 2 2 3 7 1 2 1
. Вывод: 2 2 3 7 0 1 1
.
Теперь я пытаюсь найти ошибку в своем коде. Вот оно:
#include <stddef.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "MergeSort.h"
int StrCmp(char *st1, char *st2) {
char *str1 = st1, *str2 = st2;
while (*str1 == *str2 amp;amp; *str1 != 0)
str1 , str2 ;
return *str1 - *str2;
}
int CompareInt(void *a, void *b) {
return *(int *)a - *(int *)b;
}
int CompareChar(void *a, void *b) {
return *(char *)a - *(char *)b;
}
int CompareStr(const void *a, const void *b) {
return strcmp(*(char **)a, *(char **)b);
}
int merge(void *base, size_t num, size_t el_size, int (*compar)(const void*, const void*)) {
size_t size = (size_t)num / 2;
char *char_base = (char *)base;
char *first = malloc(size * el_size);
char *second = malloc((num - size) * el_size);
for (int i = 0; i < size; i )
for (int j = 0; j < el_size; j )
first[i * el_size j] = char_base[i * el_size j];
for (int i = 0; i < num - size; i )
for (int j = 0; j < el_size; j )
second[i * el_size j] = char_base[size * el_size i * el_size j];
size_t i = 0, j = 0, c = 0;
while (i < size amp;amp; j < num - size) {
if (compar(amp;first[i * el_size], amp;second[j * el_size]) <= 0) {
for (int k = 0; k < el_size; k )
char_base[el_size * c k] = first[i * el_size k];
i ;
} else {
for (int k = 0; k < el_size; k )
char_base[el_size * c k] = second[j * el_size k];
j ;
}
c ;
}
if (i == size) {
while (j < num - size) {
for (int k = 0; k < el_size; k )
char_base[el_size * c k] = second[j * el_size k];
j ;
c ;
}
} else {
while (i < size) {
for (int k = 0; k < el_size; k )
char_base[el_size * c k] = first[i * el_size k];
i ;
c ;
}
}
free(first);
free(second);
}
int merge_sort(void *base, size_t num, size_t el_size, int (*compar)(const void*, const void*)) {
if (num > 1) {
size_t s = num / 2;
char *char_base = base;
merge_sort(char_base, s, el_size, compar);
merge_sort(char_base (num - s) * el_size, num - s, el_size, compar);
merge(char_base, num, el_size, compar);
}
}
int main() {
int nums[] = { 2, 2, 3, 7, 1, 2, 1 };
cmp_t cmp = CompareInt;
merge_sort(nums, 7, sizeof(int), cmp);
for (int i = 0; i < 7; i )
printf("%i ", nums[i]);
return 0;
}
Комментарии:
1. @RawN уже удален. Кстати, мне нравятся звезды.
2. Когда вы говорите «это не работает», что именно вы имеете в виду? Приведите краткий пример ввода и фактического вывода.
3. @MarkRansom Ввод: 2 2 3 7 1 2 1 Вывод: 2 2 3 7 0 1 1
Ответ №1:
Ошибка в функции merge_sort()
: второй рекурсивный вызов выполняется по неправильному базовому адресу:
merge_sort(char_base (num - s) * el_size, num - s, el_size, compar);
исправление с
merge_sort(char_base s * el_size, num - s, el_size, compar);
Обратите внимание, что в вашем коде есть и другие проблемы:
-
функции сравнения имеют неправильные подписи, они должны принимать
const void *
аргументы. -
оба
merge()
иmerge_sort()
должны быть определены какvoid
, поскольку они не возвращают никакого значения. -
CompareInt
невозможно обрабатывать большие целочисленные значения, разница между которыми превышаетINT_MAX
, например ,INT_MAX
иINT_MIN
. Это должно быть написано:int CompareInt(const void *a, const void *b) { int na = *(const int *)a; int nb = *(const int *)b; return (nb < na) - (na < nb); }
-
вы должны напечатать a
'n'
после чисел.
Вы также можете улучшить реализацию различными способами:
-
если вы вычисляете
s
как(n 1) / 2
, вы можете использовать меньше памяти и иметь более простую и быструю реализацию, поскольку вам не понадобитсяsecond
массив вmerge
функции. -
используя указатели, вы бы значительно сократили количество умножений.
Вот более простая реализация с той же семантикой:
void merge(void *base, size_t num, size_t el_size, size_t size,
int (*compar)(const void*, const void*)) {
size_t split = size * el_size;
char *first = malloc(split);
char *p1 = memcpy(first, base, split);
char *dest = base, *p2 = dest split;
size_t i = 0, j = size;
while (i < size) {
if (j == num || compar(p1, p2) <= 0) {
for (size_t k = 0; k < el_size; k )
*dest = *p1 ;
i ;
} else {
for (size_t k = 0; k < el_size; k )
*dest = *p2 ;
j ;
}
}
free(first);
}
void merge_sort(void *base, size_t num, size_t el_size,
int (*compar)(const void*, const void*)) {
if (num > 1) {
size_t s = (num 1) / 2;
char *char_base = base;
merge_sort(char_base, s, el_size, compar);
merge_sort(char_base s * el_size, num - s, el_size, compar);
merge(char_base, num, el_size, s, compar);
}
}
Комментарии:
1. Спасибо! Это решило мою проблему. На самом деле, я не был уверен, что мне следовало использовать
num - s
, а не простоs
.2. Я посмотрел прямо на эту строку и пропустил ее, молодец.
3. @chqrlie Спасибо за подробный ответ. Еще одна вещь, о которой я беспокоился, — это создание массивов. Должен ли я выполнять
*base_char = 0
в конце подпрограммы слияния для массивов int, строк и символов?4. @KirillVishnyakov: вовсе нет. Массив сортируется на месте,
int
и строковые массивы не нуждаются в терминаторе,char
массив должен иметь терминатор, если он используется как строка, но вы не должны включать этот терминатор в длину, к которой вы переходитеmerge_sort()
.