Оптимизация алгоритма Поиск числа максимально повторяющегося в массиве


[details="Спойлер"]
int arr[10] = {553, 555, 555, 554, 556, 555, 557, 554, 552, 555}; //Массив с числами

int arrD[10] = {0};
byte arrC[10] = {0}; //Кол-во повторов числа

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  for (byte n = 0; n < 10; n++) {
    for (byte k = 0; k < 10; k++) {
      if (arrD[k] == 0) {
        arrD[k] = arr[n];
        ++arrC[k];
        break;
      }
      if (arrD[k] == arr[n]) {
        ++arrC[k];
        break;
      }
    }
  }
  byte m = 0, ind = 0;
  for (byte n = 0; n < 10; n++) {
    if (arrD[n] == 0) break;
    Serial.print(arrD[n]); Serial.print(" = "); Serial.println(arrC[n]);
    if (arrC[n] > m) {
      ind = n;
      m = arrC[n];
    }
  }
  Serial.print("maxD: "); Serial.println(arrD[ind]);
}

void loop() {
  // put your main code here, to run repeatedly:

}

Результат:
553 = 1
555 = 4
554 = 2
556 = 1
557 = 1
552 = 1
maxD: 555
[/details]

Необходимо найти в массиве чисел число максимально повторяющееся.
Я это сделал. Но возможно ли как-то оптимизировать мой код или он написан верно? Нужна скорость работы ну и использование памяти. Я не особо в программировании, поэтому прошу посмотреть.

Вообще-то об оптимизации, как правило, имеет смысл говорить только если длина массива существенно превышает 10.
Ваш алгоритм имеет асимптотическую сложность O(N^2), тогда как в оптимальном случае он может иметь сложность не хуже O(N*log(N)). (в частных случаях - до O(N))
Но не факт, что на 10 элементах такой алгоритм будет работать быстрее Вашего.

2 лайка

А если просто отсортировать массив методом “половинная вставка” (так кажется называется?) и в момент сортировки если элемент уже существует в массиве - увеличивать его счетчик?

На выходе получим отсортированный массив без повторений (можно из него получить быстро максимальное и минимальное значение) и количество вхождений каждого элемента массива в первоначальном.

Сложность сортировки вставками O(n^2), т.е. “шило на мыло”.

А про числа в массиве arr что-то известно? Например, максимальное и минимальное значения? Если известно, то, возможно, удастся сделать это совсем просто и линейно по скорости.

Оставлю как есть. Только перенес поиск максимального количества в первый цикл.

#define COUNT_NUM 16
int arr[COUNT_NUM] = {557, 554, 555, 553, 552, 553, 555, 555, 554, 556, 555, 557, 554, 552, 555, 556}; //Массив с числами

int arrD[COUNT_NUM] = {0};
byte arrC[COUNT_NUM] = {0};

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  Serial.println(micros());
  byte MaxC = 0, indC = 0;
  for (byte n = 0; n < COUNT_NUM; n++) {
    for (byte k = 0; k < COUNT_NUM; k++) {
      if (arrD[k] == 0) {
        arrD[k] = arr[n];
        ++arrC[k];
        if (arrC[k] > MaxC) {
          indC = k;
          MaxC = arrC[k];
        }
        break;
      }
      if (arrD[k] == arr[n]) {
        ++arrC[k];
        if (arrC[k] > MaxC) {
          indC = k;
          MaxC = arrC[k];
        }
        break;
      }
    }
  }
  for (byte n = 0; n < COUNT_NUM; n++) {
    if (arrD[n] == 0) break;
    Serial.print(arrD[n]); Serial.print(" = "); Serial.println(arrC[n]);
  }
  Serial.print("maxD: "); Serial.println(arrD[indC]);
  Serial.print(micros());
}

void loop() {
  // put your main code here, to run repeatedly:

}

Без сериала быстро делается. Спасибо всем.

Скорее всего нет. В массиве будет выборка с аналогового датчика. Хочу попробовать фильтровать именно так. Так как другие фильтры(легкие, например среднее арифметическое) все равно не то.

Хотя почему нет?.. Я же их заношу в массив… конечно могу запоминать минимум и максимум.

К вашему методу тоже есть вопросы.

  • Что делать если все значения разные.
  • Что делать если все значения разные но около реального значения, и есть несколько одинаковых, но явно не достоверных.

Задача толком не объяснена, но позволю себе предположить, что это придётся делать много раз и надо вынести в функцию. Если так, то не забывайте перед началом заполнять вспомогательные массивы нулями. Это они у Вас только в первый раз “и так заполнены”, а потом в них старая грязь будет.

Опять, же толком ничего непонятно, но вот из этого:

Я делаю вывод, что в реальности значения будут не сильно отличаться друг от друга, в пределах 10-20 от min до max.

Тогда бы я ушёл от квадратичности и сделал бы линейно. Рассказывать как? Или Вас всё устраивает и с квадратичным алгоритмом?

У гайвера есть урок по фильтрам. Может быть вам медианный фильтр подойдет.

@TempArd
У вас реально хороший алгоритм. Я написал без второго массива - но как ни старался, он получается медленнее вашего процентов на 20-25, в зависимости от данных. Зато расход памяти у меня чуть ниже.

1 лайк

Я думал об этом. Как вариант, если MaxC < COUNT_NUM / 3 то применить арифметическое усреднение. Надеясь, что таких случаев будет мало…
По второму вопросу …вряд ли так будет. Но, надо тогда подумать , если это станет проблемой.

Спасибо, учту. А так, да, это будет отдельной функцией.

Именно так. Но в частных случаях эти значения будут либо увеличиваться либо уменьшаться. Данные с датчика давления воды. (Идет набор воды, идет расход воды, и собсно третье, когда система в покое и шумит датчик) На этот случай у меня пока решение такое, Брать минимальное при постоянном уменьшении(расходе) и максимальное при увеличении(наборе), ну или последние 3 значения усредненные.

Если не затруднит и я в этом разберусь, то конечно вариант посмотрю. Искал перед вопросом в интернете, но не смог найти для себя решение. И как-то неожиданно в башке родился алгоритм. Хотя я далеко не программист, но что-то могу)

Я смотрел его фильтры. И обратил внимание на медийный. Но подумав, что может оказаться так:

4
4
5
6
7
7
7
Результат будет 6, хотя меня больше устроит 7

А вообще, нужны “полевые испытания”… пока готовлюсь. Может и медийный пойдет.

Спасибо. Возможно мне будет важна память а не скорость. Но пока и то и другое важно)

Как говорится дуракам везет))) Ну или новичкам) Хотя, я давно связан с программированием, но далеко не программист нисколько вообще… (хобби на которое катастрофически не хватает времени :()

Со старого форума аккаунты не перенеслись?

Перенеслись, но без паролей. Нужно делать сброс пароля.

  1. узнаёте минимальное и максимальное значение данных в массиве. Это делается за 1 проход по массиву. Допустим dataMin и dataMax;
  2. заводите вспомогательный массив байтов tmpBuf размером dataMax - dataMin и заполняете его нулями;
  3. делаете один проход по основному массиву: для каждого значения val из основного массива, увеличиваете на 1 значение tmpBuf[val - dataMin].
  4. делаете один проход по вспомогательному массиву с целью запомнить индекс максимального значения в нём (idxMax).
  5. результат: число dataMin + idxMax - наиболее часто встречается в исходном массиве.

Для проверки и сравнения, я вынес Ваш квадратичный метод в функцию findMax; написал вышеописанный линейный метод в виде функции linearFindMax (в ней, вместо подробных комментариев, проставлены номера шагов алгоритма, описанного выше); написал функцию тестирования testOneMethod, которая вызывает переданную ей функцию 20 тысяч раз; а в функции setup вызвал testOneMethod дважды - для квадратичного и линейного алгоритмов.

Результат, полученный на Nano приведен внизу текста программы. Как видите, линейный метод выигрывает по времени в полтора раза. Что касается памяти, то в линейном массив размером “в разброс данных” против двух массивов равных размеру исходного массива в квадратичном. Кто выигрывает, зависит от “разброса данных”.

Текст тестового скетча
#include <stdint.h>

int arr[10] = {553, 555, 555, 554, 556, 555, 557, 554, 552, 555}; //Массив с числами
constexpr size_t arrSize = sizeof(arr) / sizeof(arr[0]);

int findMax(void) {
	int arrD[arrSize] = {0};
	byte arrC[arrSize] = {0}; //Кол-во повторов числа
	for (byte n = 0; n < arrSize; n++) {
		for (byte k = 0; k < arrSize; k++) {
			if (arrD[k] == 0) {
				arrD[k] = arr[n];
				++arrC[k];
				break;
			}
			if (arrD[k] == arr[n]) {
				++arrC[k];
				break;
			}
		}
	}
	byte m = 0, ind = 0;
	for (byte n = 0; n < arrSize; n++) {
		if (arrD[n] == 0) break;
			//Serial.print(arrD[n]); Serial.print(" = "); Serial.println(arrC[n]);
			if (arrC[n] > m) {
			ind = n;
			m = arrC[n];
		}
	}
	//Serial.print("maxD: "); Serial.println(arrD[ind]);
	return arrD[ind];
}

int linearFindMax(void) {
	//
	// ШАГ №1
	//
	int dataMax = INT16_MIN;
	int dataMin = INT16_MAX;
	for (uint8_t i = 0; i < arrSize; i++) {
		const int ai = arr[i];
		if (ai > dataMax) dataMax = ai;
		if (ai < dataMin) dataMin = ai;
	}
	//
	// ШАГ №2
	//
	const size_t tmpSize = dataMax - dataMin;
	uint8_t * tmpBuf = reinterpret_cast<uint8_t *>(calloc(tmpSize, sizeof(uint8_t)));
	if (! tmpBuf) {
		Serial.println("Что-то с памятью моей стало :(");
		return 0;
	}
	//
	// ШАГ №3
	//
	for (uint8_t i = 0; i < arrSize; i++) ++tmpBuf[arr[i] - dataMin];
	//
	// ШАГ №4
	//
	uint8_t indMax = 0, maxRep = 0;
	for (uint8_t i = 0; i < tmpSize; i++) {
		const uint8_t tbi = tmpBuf[i];
		if (tbi > maxRep) {
			maxRep = tbi;
			indMax = i;
		}
	}
	free (tmpBuf); // Массив tmpBuf больше не нужен, освобождаем память
	//
	// ШАГ №5
	//
	return dataMin + indMax;
}

void testOneMethod(const char * title, int (* f)(void)) {
	uint32_t sum = 0;
	constexpr int total = 20000;
	const uint32_t start = millis();
	for (uint16_t cnt = 0; cnt < total; cnt++) sum += f();
	const uint32_t duration = millis() - start;
	Serial.print("***** \"");
	Serial.print(title);
	Serial.println("\" test");
	Serial.print("Результат: ");
	Serial.println(sum / total);
	Serial.print("Время: ");
	Serial.print(duration);
	Serial.println(" мс");
}

void setup(void) {
	// put your setup code here, to run once:
	Serial.begin(9600);

	testOneMethod("Square", findMax);
	testOneMethod("Linear", linearFindMax);
}

void loop(void) {}

///////////////
//
//	Результат
//
//	***** "Square" test
//	Результат: 555
//	Время: 1525 мс
//	***** "Linear" test
//	Результат: 555
//	Время: 999 мс
1 лайк

Спасибо, Евгений. Я разберусь в этом варианте. Но уже сразу вижу, что шаг № 1 можно убрать. А Макс и Мин можно запоминать при заполнении массива… (при считывании значений с датчика)

Разобрался.) Чем меньше разница между мин и макс, тем быстрее и меньше памяти…
Во временном массиве кол-ва вхождений чисел индекс не само число, как в примерах для мелких чисел (0…9) в интернете, а разница от числа из массива и меньшего значения числа из этого массива. Хитро! :+1: