Попытка понять алгоритм сортировки вставок - PullRequest
6 голосов
/ 12 сентября 2011

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

Пытаясь проанализировать фрагменты кода на Python Idle, я знаю, что:

key (holds variables) = 8, 2, 4, 9, 3, 6

и что:

i (holds the length) = 7 ( 1, 2, 3, 4, 5, 6, 7)

Я не знаю, почему 1 используется в первой строке: range (1, len (mylist)).Любая помощь приветствуется.

mylist = [8, 2, 4, 9, 3, 6]

for j in range(1,len(mylist)):
    key = mylist[j]
    i = j
    while i > 0 and mylist[i-1] > key:
        mylist[i] = mylist[i - 1]
        i -= 1
        mylist[i] = key

Ответы [ 7 ]

12 голосов
/ 12 сентября 2011

Позвольте мне попытаться сломать это.

Начните с рассмотрения списка.Это "почти" отсортировано.То есть первые несколько элементов отсортированы, но последний элемент не отсортирован.Так что это выглядит примерно так:

[10, 20, 30, 50, 15]

Очевидно, 15 находится не в том месте.Так как мы это передвинем?

    key = mylist[4]
    mylist[4] = mylist[3]
    mylist[3] = key

Это переключится вокруг 15 и 50, поэтому теперь список выглядит так:

[10, 20, 30, 15, 50]

Но мы бы хотели сделать это несколько раз в цикле.Чтобы сделать это, мы можем сделать:

while ???:
    key = mylist[i]
    mylist[i] = mylist[i-1]
    mylist[i-1] = key
    i -= 1

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

Давайте еще раз посмотрим на наш список и ходы, которые мы хотим сделать:

[10, 20, 30, 50, 15]
[10, 20, 30, 15, 50]
[10, 20, 15, 30, 50]
[10, 15, 20, 30, 50]
# stop! we are sorted now!

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

key = mylist[i]
while key < mylist[i-1]:
    mylist[i] = mylist[i-1]
    mylist[i-1] = key
    i -= 1

Хорошо, но произойдет, если мы сейчас попытаемся отсортировать этот список:

[10, 20, 1] [10, 1, 20][1, 10, 20] # ОШИБКА !!

В этот момент происходит что-то плохое.Мы пытаемся проверить, является ли ключ

Если мы достигнем начала списка, мы не сможем продвинуть нашу пивот / клавишу дальше, поэтому мы должны остановиться.Мы обновляем наш цикл while для обработки этого:

key = mylist[i]
while i > 0 and key < mylist[i-1]:
    mylist[i] = mylist[i-1]
    mylist[i-1] = key
    i -= 1

Так что теперь у нас есть метод сортировки почти отсортированного списка.Но как мы можем использовать это для сортировки всего списка?Мы сортируем части списка за раз.

 [8, 2, 4, 9, 3, 6]

Сначала мы сортируем первые 1 элементы:

 [8, 2, 4, 9, 3, 6]

Затем мы сортируем первые 2 элемента:

 [2, 8, 4, 9, 3, 6]

Затем мы сортируем первые 3 элемента

 [2, 4, 8, 9, 3, 6]

И так далее и тому подобное

 [2, 4, 8, 9, 3, 6]
 [2, 4, 8, 9, 3, 6]
 [2, 3, 4, 8, 9, 6]
 [2, 3, 4, 6, 8, 9]

Но как нам это сделать?С циклом for

for j in range(len(mylist)):
    i = j
    key = mylist[i]
    while i > 0 and key < mylist[i-1]:
        mylist[i] = mylist[i-1]
        mylist[i-1] = key
        i -= 1 

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

for j in range(1, len(mylist)):
    i = j
    key = mylist[i]
    while i > 0 and key < mylist[i-1]:
        mylist[i] = mylist[i-1]
        mylist[i-1] = key
        i -= 1 

Несколько незначительных изменений, которые не имеют значениявозвращает нас к исходному коду

for j in range(1, len(mylist)):
    key = mylist[j]
    i = j
    while i > 0 and key < mylist[i-1]:
        mylist[i] = mylist[i-1]
        i -= 1 
        mylist[i] = key
6 голосов
/ 12 сентября 2011

Алгоритм сортировки вставкой работает, пытаясь создать отсортированный список возрастающей длины в начале массива.Идея состоит в том, что если вы начнете с создания одноэлементного отсортированного списка в начале, затем двухэлементного списка, затем трехэлементного списка и т. Д., То, как только вы построите n-элементный отсортированный список, вы отсортировали весь массив и все готово.

Например, учитывая массив

3  1  4

Мы можем разделить это на отсортированный список с нулевым элементом и несортированный список с тремя элементами:

| 3  1  4

Теперь мы добавляем 3 в наш отсортированный список.Поскольку этот список теперь содержит всего один элемент, он автоматически сортируется:

3 | 1  4

Теперь мы хотим добавить 1 в наш отсортированный список.Если мы просто добавим 1 в конец списка следующим образом:

3 1 | 4

, то отсортированный список больше не будет отсортирован.Чтобы исправить это, внутренний цикл кода вставки сортировки работает, постоянно меняя 1 с элементом перед ним, пока он не будет в правильном положении.В нашем случае мы меняем 1 и 3:

1 3 | 4

, и поскольку 1 теперь находится в начале массива, нам больше не нужно его перемещать.Вот почему внутренний цикл работает пока i > 0;как только индекс нового элемента (i) находится в начале массива, перед ним нет ничего, что могло бы быть больше.

Наконец, мы обновляем массив, добавляя 4 в отсортированный список.Поскольку он находится в отсортированной позиции, мы закончили:

1 3 4

И наш массив теперь в отсортированном порядке.

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

2  7  1  8

Алгоритм сортировки вставок может попытаться разделить этот массив следующим образом, поместив пустой отсортированный список впереди:

| 2  7  1  8

Но немного более быстрый вариант - разделить список следующим образом:

2 | 7  1  8

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

Это действительно оптимизацияалгоритма со стороны авторов.Алгоритм будет прекрасно работать, если внешний цикл начнется с нуля, но они просто решили запустить его с нуля, чтобы избежать ненужной итерации цикла.

Надеюсь, это поможет!

2 голосов
/ 12 сентября 2011

Посмотрите на петлю while. Он начинается с i, имеющего значение 1, но затем i уменьшается. Таким образом, в последней строке минимальное значение i может быть 0, что является первым элементом в списке. Если вы начнете с 0, i станет -1, что допустимо в python, но означает последний элемент. Поэтому диапазон должен начинаться с 1.

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

1 голос
/ 12 сентября 2011

Проверьте анимированные InsertionSort ЗДЕСЬ

1 голос
/ 12 сентября 2011

j-итерация вставляет j-й элемент в отсортированные элементы перед j.Так что нет смысла начинать с j = 0.В случае j = 1 подсписок ниже равен myList[0:1], который всегда сортируется, и цикл вставляет myList[1] в подсписок myList[0:2]

1 голос
/ 12 сентября 2011

Позже устанавливается i = j и осуществляется доступ к myList[i-1].Итак, j должно быть j >= 1.

Добавлено : установка j = 0 логически неверна, потому что в цикле осуществляется доступ к myList[j-1] - это просто путем статического анализакод (и зная, что я = J).Даже если это не может произойти во время выполнения из-за while i > 0, это по крайней мере бессмысленно.Если в коде присутствует выражение myList[j-1], то оно обязательно должно быть j >= 1.

1 голос
/ 12 сентября 2011

Причина в том, что:

i = j

и этот mylist доступен как:

mylist[i - 1]

Поэтому первое значение равно 0. Если бы диапазон начинался с 0, это привело бы к доступу к mylist в позиции -1.

...