Сортировка связного списка

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

Объединение двух упорядоченных списков

Пусть у нас есть односвязный список, элементы которого описаны структурой (пример на языке Pascal):

  type
    PList_Item = ^TList_Item;
    TList_Item = record
      Key: Integer;
      Next: PList_Item;
    end;
  var
    List: PList_Item; // Указатель на список

Алгоритм достаточно прост: на входе имеются указатели на первые элементы объединяемых списков. Началом результирующего списка из них выбирается элемент с наименьшим ключом. Затем в качестве следующих элементов результирующего списка выбирается последующие элементы из первого или второго исходного списка, с меньшим значением ключа. Когда достигнут последний элемент одного из исходных списков, указатель последнего элемента результирующего списка устанавливается на остаток другого входного списка.

  function IntersectSorted(const pList1, pList2: PList_Item): PList_Item;
  var
    pCurItem: PList_Item;
    p1, p2: PList_Item;
  begin
    p1 := pList1;
    p2 := pList2;
    if p1^.Key <= p2^.Key then 
    begin
      pCurItem := p1;
      p1 := p1^.Next;
    end 
    else begin
      pCurItem := p2;
      p2 := p2^.Next;
    end;
    Result := pCurItem;
    while (p1 <> nil) and (p2 <> nil) do
    begin
      if p1^.Key <= p2^.Key then
      begin
        pCurItem^.Next := p1;
        pCurItem := p1;
        p1 := p1^.Next;
      end 
      else begin
        pCurItem^.Next := p2;
        pCurItem := p2;
        p2 := p2^.Next;
      end;
    end;
    if p1 <> nil then
      pCurItem^.Next := p1
    else
      pCurItem^.Next := p2;
  end;

Сортировка односвязного списка

Процесс сортировки списка представляет из себя последовательный проход по списку с сортировкой сначала пар элементов, затем каждой двойки пар элементов, с объединением в списки из 4х элементов, затем объединяются получившиеся списки из 8, 16 и т. д. элементов.

В предложенной реализации используется стек списков. Необходимый размер стека равен [log2n] + 1, где n — количество элементов списка. Если количество элементов заранее не известно, то можно заранее заложить достаточную глубину стека. Так, например, стек глубиной в 32 элемента может быть использован для сортировки списков длиной до 4 294 967 295 элементов. В стеке сохраняются указатели на отсортированные части списка и уровень — число фактически равное log2i + 1, где i — количество элементов в этой части списка.

Суть алгоритма в следующем: идёт последовательный обход списка, при этом каждый элемент преобразуется к вырожденному списку, путём удаления указателя на следующий элемент. Указатель на созданный таким образом список помещается в стек, при этом уровень указывается равным 1, после чего проводится проверка: если два последних элемента стека имеют одно и то же значение уровня, то они извлекаются из стека, производится объединение списков, на которые указывают эти элементы, а результирующий список помещается в стек с уровнем на единицу больше предыдущего. Такое объединение повторяется пока уровни двух последних элементов равны, или пока не будет достигнута вершина стека. После того как исходный список полностью пройден, перечисленные в стеке списки объединяются вне зависимости от их уровня. Получившийся в результате объединения список и есть искомый, с отсортированными элементами

type
  TSortStackItem = record
    Level: Integer;
    Item: PList_Item;
  end;
var
  Stack: Array[0..31] of TSortStackItem;
  StackPos: Integer;
  p: PList_Item;
begin
  StackPos := 0;
  p := List;
  while p <> nil do
  begin
    Stack[StackPos].Level := 1;
    Stack[StackPos].Item := p;
    p := p^.Next;
    Stack[StackPos].Item^.Next := nil;
    Inc(StackPos);
    while (StackPos > 1) and (Stack[StackPos - 1].Level = Stack[StackPos - 2].Level) do 
    begin
      Stack[StackPos - 2].Item := IntersectSorted(Stack[StackPos - 2].Item, Stack[StackPos - 1].Item);
      Inc(Stack[StackPos - 2].Level);
      Dec(StackPos);
    end;
  end;
  while StackPos > 1 do
  begin
    Stack[StackPos - 2].Item := IntersectSorted(Stack[StackPos - 2].Item, Stack[StackPos - 1].Item);
    Inc(Stack[StackPos - 2].Level);
    Dec(StackPos);
  end;
  if StackPos > 0 then
    List := Stack[0].Item;
end;

Сложность алгоритма

Очевидно, сложность алгоритма O(n log n), при этом требования к памяти минимальны O(log n)

Сортировка двусвязного списка

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

  type
    PList_Item = ^TList_Item;
    TList_Item = record
      Key: Integer;
      Prev, Next: PList_Item; 
    end;
  var
    // Указатели на первый и последний элемент списка
    First, Last: PList_Item;
  p := First;
  // Проверка, что список не является пустым
  if p <> nil then
  begin  
    p^.Prev := nil;
    while p^.Next <> nil do
    begin
      p^.Next^.Prev := p;
      p := p^.Next;
    end;
  end;
  Last := p;

См. также

Литература

This article is issued from Wikipedia. The text is licensed under Creative Commons - Attribution - Sharealike. Additional terms may apply for the media files.