Использование минимальных / максимальных значений типа при поиске минимальных / максимальных значений в наборе - PullRequest
3 голосов
/ 04 января 2011

Первоначально, я в основном написал эссе с вопросом в конце, так что я собираюсь свести его к следующему: что лучше (быть действительно привередливым здесь)?

A)

int min = someArray[0][0];
for (int i = 0; i < someArray.length; i++)
    for (int j = 0; j < someArray[i].length; j++)
        min = Math.min(min, someArray[i][j]);

-или-

В)

int min = int.MAX_VALUE;
for (int i = 0; i < someArray.length; i++)
     for (int j = 0; j < someArray[i].length; j++)
         min = Math.min(min, someArray[i][j]);

Я считаю, что b быстрее, сохраняя одну или две инструкции, инициализируя min постоянным значением вместо использования индексатора. Это также кажется менее излишним - не сравнивая someArray[0][0] с самим собой ...

В качестве алгоритма , который лучше / правильнее.

РЕДАКТИРОВАТЬ: Предположим, что массив не является нулевым и не пустым.
EDIT2: исправлена ​​пара неосторожных ошибок.

Ответы [ 3 ]

1 голос
/ 04 января 2011

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

Причина, по которой эти алгоритмы эквивалентны, связана с классным математическим объектом, называемым полурешёткой . Чтобы мотивировать полурешетки, есть несколько интересных свойств max, которые оказываются справедливыми:

  1. max = idempotent , поэтому применение его к одному и тому же значению дважды возвращает исходное значение: max (x, x) = x
  2. max равно коммутативно , поэтому не имеет значения, в каком порядке вы применяете его к аргументам: max (x, y) = max (y, x)
  3. max равно ассоциативно , поэтому, принимая максимум три или более значений, не имеет значения, как вы группируете элементы: max (max (x, y), z) = max (x, max (y, z))

Эти законы также применимы как к минимуму, так и ко многим другим структурам. Например, если у вас есть древовидная структура, оператор «наименьшей верхней границы» также удовлетворяет этим ограничениям. Точно так же, если у вас есть набор множеств и объединение или пересечение множеств, вы обнаружите, что эти ограничения также выполняются.

Если у вас есть набор элементов (например, целые числа, строки и т. Д.) И некоторый двоичный оператор, определенный над ними с указанными выше тремя свойствами (идемпотентность, коммутативность и ассоциативность), то вы нашли структуру, называемую полурешетка . Затем бинарный оператор называется оператором встречи (или иногда оператор соединения в зависимости от контекста).

Причина, по которой полурешетки полезны, состоит в том, что если у вас есть (конечная) коллекция элементов, взятых из полурешетки, и вы хотите вычислить их соответствие, вы можете сделать это с помощью цикла, подобного следующему:

Element e = data[0];
for (i in data[1 .. n])
    e = meet(e, data[i])

Причина, по которой это работает, заключается в том, что, поскольку оператор встречи является коммутативным и ассоциативным, мы можем применить встречу к элементам в любом порядке, который нам нужен. Применяя его по одному элементу за раз, когда мы проходим по элементам массива по порядку, мы получаем такое же значение, как если бы мы сначала перетасовывали элементы массива или повторяли в обратном порядке и т. Д. В вашем случае оператор встречи был " max "или" min ", и так как они удовлетворяют законам для операторов удовлетворения, описанным выше, приведенный выше код будет правильно вычислять max или min.

Чтобы ответить на ваш первоначальный вопрос, нам нужно немного больше терминологии. Вам было любопытно, было ли лучше или безопаснее инициализировать исходное предположение о минимальном значении, которое будет максимально возможным целым числом. Причина, по которой это работает, в том, что у нас есть классное свойство, которое

min(int.MAX_VALUE, x) = min(x, int.MAX_VALUE) = x

Другими словами, если вы вычислите встречу int.MAX_VALUE и любое другое значение, вы получите второе значение обратно. В математических терминах это происходит потому, что int.MAX_VALUE является верхним элементом полурешетки встречи. Более формально, верхний элемент для полурешетки Meet - это элемент (обозначаемый как & top;), удовлетворяющий

встреча (& top;, x) = встреча (x, & top;) = x

Если вы используете max вместо min, тогда верхний элемент будет int.MIN_VALUE, так как

max(int.MIN_VALUE, x) = max(x, int.MIN_VALUE) = x

Потому что применение оператора Meet к & top; и любой другой элемент создает этот другой элемент. Если у вас есть полурешетка встречи с четко определенным верхним элементом, вы можете переписать приведенный выше код, чтобы вычислить соответствие всех элементов как

Element e = Element.TOP;
for (i in data[0 .. n])
    e = meet(e, data[i])

Это работает, потому что после первой итерации e устанавливается на meet(e, data[0]) = meet(Element.TOP, data[0]) = data[0] и итерация продолжается как обычно. Следовательно, в вашем первоначальном вопросе не имеет значения, какой из двух циклов вы используете; до тех пор, пока определен хотя бы один элемент, они дают одинаковое значение.

Тем не менее, не все полурешетки имеют верхний элемент. Рассмотрим, например, набор всех строк, где оператор встречи определен как

meet(x, y) = x if x lexicographically precedes y
           = y otherwise

Например, meet("a", "ab") = "a", meet("dog, "cat") = "cat" и т. Д. В этом случае нет строки s, удовлетворяющей свойству meet (s, x) = meet (x, s) = x, и поэтому полурешетка имеетнет верхнего элемента.В этом случае вы не сможете использовать вторую версию кода, потому что нет верхнего элемента, для которого вы можете инициализировать начальное значение.

Однако есть очень симпатичная техника, которую вы можете использовать для фальсификации этого, что на самом деле в конечном итоге привыкнуть немного на практике.Для заданной полурешетки без верхнего элемента вы можете создать новую полурешетку, у которой действительно есть верхний элемент, введя новый элемент ⊤ и произвольно определив, что встретить (⊤, x) = meet (x, ⊤) = x.Другими словами, этот элемент специально создан как верхний элемент и не имеет никакого значения в противном случае.

В коде вы можете вводить такой элемент неявным образом, написав

bool found = false;
Element e;

for (i in data[0 .. n]) {
    if (!found) {
        found = true;
        e = i;
    } else {
        e = meet(e, i);
    }
}

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

Надеюсь, это поможет!Извините, если это слишком теоретически ... Мне просто нравится математика.: -)

0 голосов
/ 04 января 2011

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

С точки зрения алгоритмической чистоты я понятия не имею, лучше ли одно, чем другое.

Кстати, вариант А должен иметь свою инициализацию следующим образом:

int min = someArray[0][0];
0 голосов
/ 04 января 2011

B лучше; если someArray окажется пустым, вы получите ошибку времени выполнения; Но у A и B могут возникнуть проблемы, потому что если someArray имеет значение null (и это не было проверено в предыдущих строках кода), и A, и B будут выдавать исключения.

...