Алгоритмы сортировки и поиска Задача поиска. Задан массив A из n элементов и некоторое значение p, (поисковое). Требуется найти такой номер i, что A[i]=p. Результат поиска: 1) существует единственный элемент с номером i, для которого A[i]=p; 2) A[i]≠p при любых i=1,2,...,n; 3) существует несколько элементов с номерами i1,i2,... таких, что A[i1]=p,A[i2]=p,... ___________________________________________ Алгоритм поиска одного элемента в неупорядоченном массиве: ___________________________________________ i:=0; k:=1; while k<=n do if A[k]=p then begin i:=k; k:=n+1 end else k:=k+1; __________________________________________ Трудоемкость в наихудшем: O(n). Дихотомический поиск в упорядоченном массиве Область подозрительных элементов: A[b], A[b+1], ...,A[e]. Вначале: b=1,e=n. Сравнение поискового значения p с элементом A[c], где c=(b+e)div 2. Возможны два исхода (если существует искомый элемент): 1) A[c]<p. Искомый элемент среди: A[c+1],...,A[e]; 2) A[c]≥p. Искомый элемент среди: A[b],...,A[c]. В 1-м случае: b:=c+1, в 2-м: e:=c. __________________________________________ b:=1; e:=n; {1} while b<e do {2} begin {3} c:=(b+e)div 2; {4} if A[c]<p then b:=c+1 {5} else e:=c {6} end; {7} if A[b]=p then i:=b {8} else i:=0 {9} __________________________________________ Предусловие: b:=1, e:=n. Постусловие: b=e. Инвариант: Искомый элемент (если он существует) находится среди: A[b], ...,A[e]. Трудоемкость алгоритма. Пусть: 2m – 1 < k ≤ 2m , (*) где k – размер подозрительной области Вначале k = n. При четном k размеры области уменьшаются ровно в два раза, а при нечетном – в два с округлением, неравенство (*) сохраняется. После m = log 2 n шагов: k = 1. Общее количество сравнений C в наихудшем случае: C log 2 n 1. _________________________________________ В упорядоченном массиве элементы, равные поисковому значению, располагаются подряд. Программой ищется номер начального элемента такой группы. Чтобы отыскать номер последнего элемента в этой группе, программу следует изменить: c:=(b+e+1)div 2; {4} if A[c]<=p then b:=c {5} else e:=c-1 {6} __________________________________________ Поиск в списках 1. Поиск значения S0 путем последовательного просмотра в линейном списке: _________________________________________ p0:=p; while (p0<>nil)and(p0^.s<>S0) do p0:=p0^.p _________________________________________ Инвариант цикла (указатель p0 указывает на i-й элемент): «среди элементов списка с номерами 1, …, i – 1 содержимое ни одного из них не равно S0.» _________________________________________ 2. Поиск значения S0 в упорядоченном по возрастанию списке: p0:=p; while (p0<>nil)and(p0^.s<S0) do p0:=p0^.p; if (p0<>nil)and(p0^.s>S0) then p0:=nil _________________________________ Задачи с упорядоченными (полностью или частично) массивами 1. Удаление повторяющихся значений элементов в упорядоченном массиве: __________________________________________ m:=1; C[1]:=A[1]; for i:=2 to n do if C[m]<A[i] then begin m:=m+1; C[m]:=A[i] end Инвариант: среди элементов {C[1],…,C[m]} имеются все, которые есть среди {A[1],…,A[i-1]}, но без повторений. __________________________________________ 2. В заданном числовом массиве A из n элементов требуется определить начало и конец упорядоченного отрезка, имеющего максимальную длину: b:=1; bm:=1; em:=1; for i:=1 to n do if (i=n)or(A[i]>A[i+1]) then begin {обнаружен конец очередного отрезка} if (i-b)>(em-bm) then begin bm:=b; em:=i end; b:=i+1 end ___________________________________ Алгоритмы сортировки Простейший алгоритм сортировки: i:=1; while i<n do if X[i]<=X[i+1] then i:=i+1 else begin z:=X[i]; X[i]:=X[i+1]; X[i+1]:=z; i:=1 end __________________________________ Инвариант: {X[1] ≤ X[2] ≤ . . . ≤ X[i]}, «Набор значений в массиве X остается неизменным» Инверсия: если X[i]>X[j]} , при i<j. Максимальное количество инверсий (в наихудшем): если j=2, то 1, если j=3, то 2, . . . если j=n, то n – 1. Всего: 1 + 2 + . . . + (n – 1) = n·(n – 1)/2. Пусть: {X[1]≤X[2]≤ ... ≤X[i]}, X[i]>X[i+1] Чтобы устранить одну инверсию, потребуется i шагов цикла. Чтобы устранить i инверсий в начале массива, если было: X[1]≥X[i+1] , чтобы после этого выполнялось: {X[1]≤X[2]≤ ... ≤X[i]≤X[i+1]}, потребуется i+(i-1)+...+1 = i*(i+1)/2 шагов цикла. Т.о., чтобы устранить все инверсии, потребуется 1·(1+1)/2 + 2·(2+1)/2 + . . . + (n–1)·n/2 ≈ n3/6. Т.е. трудоемкость алгоритма в наихудшем имеет порядок O(n3). Трудоемкость в наилучшем – O(n). _________________________________________ Улучшение простейшего алгоритма сортировки i:=1; while i<n do if X[i]<=X[i+1] then i:=i+1 else begin z:=X[i]; X[i]:=X[i+1]; X[i+1]:=z; if i>1 then i:=i-1 end _________________________________________ Трудоемкость. Если на каком-либо этапе работы алгоритма оказалось, что: X[1]<=X[2]<=...<= X[i], X[i]>X[i+1], то, чтобы для элементов в начале массива выполнялись соотношения X[1]<=X[2]<=...<= X[i+1], в наихудшем случае цикл должен выполниться 2i раз. А для того чтобы полностью упорядочить массив, потребуется не более 2 + 4 + … + 2(n – 1) = n (n – 1) выполнений цикла. Трудоемкость алгоритма в наихудшем имеет порядок O(n2). __________________________________________ Алгоритм обменной сортировки for i:=1 to n-1 do begin j:=i; while (j>0)and(X[j]>X[j+1]) do begin z:=X[j]; X[j]:=X[j+1]; X[j+1]:=z; j:=j-1; end end Доказательство: метод абстракции Инвариант внутреннего цикла: {X[1] ≤ X[2] ≤ . . . ≤ X[j]}, {X[j+1] ≤ X[j+2] ≤ . . . ≤ X[i]}, «Набор значений в массиве X остается неизменным» Инвариант внешнего цикла: {X[1] ≤ X[2] ≤ . . . ≤ X[i]}, «Набор значений в массиве X остается неизменным» __________________________________________ Общее количество выполнений внутреннего цикла в наихудшем случае равно 1 + 2 + . . . + (n – 1) = n·(n – 1)/2. Если массив уже упорядочен, то внутренний цикл ни разу не будет исполняться, и тогда трудоемкость алгоритма (в наилучшем!) имеет порядок O(n). __________________________________________ Алгоритм пузырьковой сортировки for i:=n downto 2 do for j:=1 to i-1 do if X[j]>X[j+1] then begin z:=X[j]; X[j]:=X[j+1]; X[j+1]:=z end Общее количество выполнений внутреннего цикла равно n·(n – 1)/2. После очередного выполнения внутреннего цикла наибольший среди первых i элементов окажется на i-м месте в массиве. ________________________________________ Сортировка линейных списков pk:=nil; while pk<>p do begin p1:=p; p2:=p1^.p; while p2<>pk do begin if p1^.s>p2^.s then begin z:=p1^.s; p1^.s:=p2^.s; p2^.s:=z end; p1:=p2; p2:=p2^.p end; pk:=p1 end Инвариант внутреннего цикла: 1) указатель p1 – на j-й элемент списка; 2) указатель p2 – на (j + 1)-й элемент списка; 3) j-й элемент списка имеет максимальное содержимое (поле s) среди элементов списка с 1-го по j-й; 4) набор значений (полей s) всех элементов списка остается неизменным. Инвариант внешнего цикла: 1) указатель pk – на (i + 1)-й элемент списка; 2) элементы списка с (i + 1)-го по n-й – упорядочены по возрастанию; 3) любой элемент списка с 1-го по i-й имеет содержимое (поле s) не больше чем содержимое элементов списка с (i + 1)-го по последний. __________________________________________ Косвенная упорядоченность Задается дополнительный массив In, называемый индексным: A[In[1]]≤ A[In[2]]≤ ... ≤A[In[n]] Модификация алгоритма сортировки: 1) вначале элементам массива In присвоить начальные значения: In[1]=1, In[2]=2, …, In[n]=n; 2) везде в программе, где элемент массива A[j] используется в операции сравнения, заменить A[j] на A[In[j]]; 3) везде в программе, где элемент массива A[j] используется в присваивании, заменить A[j] на In[j]. _________________________________________ Задача слияния двух упорядоченных массивов A и B в массив C Алгоритм слияния. Входные массивы A и B содержат соответственно n1 и n2 элементов, результирующий массив C будет содержать n1+n2 элементов. i1:=1; i2:=1; {1} for j:=1 to n1+n2 do {2} if i1>n1 then {3} begin C[j]:=B[i2]; i2:=i2+1 end{4} else if i2>n2 then {5} begin C[j]:=A[i1]; i1:=i1+1 end{6} else if A[i1]<=B[i2] then {7} begin C[j]:=A[i1]; i1:=i1+1 end{8} else {9} begin C[j]:=B[i2]; i2:=i2+1 end{10} Инвариант цикла: 1) {C[1],...,C[j-1]}= {A[1],...,A[i1-1],B[1],..., B[i2-1]}; 2) C[1]<=C[2]<=...<=C[j-1]; 3) C[j-1]<=A[i1] при i1<=n1, C[j-1]<=B[i2] при i2<=n2. _________________________________________ 2-й вариант алгоритма слияния i1:=1; i2:=1; j:=1; while (i1<=n1)and(i2<=n2) do begin if A[i1]<=B[i2] then begin C[j]:=A[i1]; i1:=i1+1 end else begin C[j]:=B[i2]; i2:=i2+1 end; j:=j+1 end; while i1<=n1 do begin C[j]:=A[i1]; i1:=i1+1; j:=j+1 end; while i2<=n2 do begin C[j]:=B[i2]; i2:=i2+1; j:=j+1 end; Слияние двух упорядоченных по возрастанию линейных списков _______________________________________ procedure slist(var p1,p2,p3:pel); var p4:pel; begin if p1^.s<=p2^.s then begin p3:=p1; p4:=p1; p1:=p1^.p end else begin p3:=p2; p4:=p2; p2:=p2^.p end; {наименьший элемент перемещен в выходной список} while (p1<>nil)and(p2<>nil) do {цикл, пока оба входных списка не пусты} if p1^.s<=p2^.s then begin p4^.p:=p1; p4:=p1; p1:=p1^.p end else begin p4^.p:=p2; p4:=p2; p2:=p2^.p end; {в цикле наименьший элемент – в выходной список} if p1<>nil then p4^.p:=p1 else p4^.p:=p2; {оставшийся непустым входной список присоединяется в конец выходного списка} p1:=nil; p2:=nil end; Алгоритм слияния в виде процедуры procedure S(var X,Y:mas; b1,e1,e2:integer); var i1, i2, j:integer; begin i1:=b1; i2:=e1+1; j:=b1; while (i1<=e1)and(i2<=e2) do begin if X[i1]<=X[i2] then begin Y[j]:=X[i1]; i1:=i1+1 end else begin Y[j]:=X[i2]; i2:=i2+1 end; j:=j+1 end; while i1<=e1 do begin Y[j]:=X[i1]; i1:=i1+1; j:=j+1 end; while i2<=e2 do begin Y[j]:=X[i2]; i2:=i2+1; j:=j+1 end end; __________________________________________ Алгоритм сортировки слиянием procedure sort(var X,Y:mas; b,e:integer); var c:integer; begin if b<e then begin c:=(b+e)div 2; sort(X,Y,b,c); sort(X,Y,c+1,e); S(X,Y,b,c,e); for i:=b to e do X[i]:=Y[i] end end; _________________________________________ Доказательство Базис. Размер N упорядочиваемого фрагмента в массиве X равен 1. Алгоритм ничего не делает. Предположение. Пусть алгоритм умеет упорядочивать фрагменты в массиве X размером от 1 до N = k ≥ 1 элементов. Индукция. Пусть фрагмент в массиве X имеет размер N=k+1≥2 элементов. Тогда алгоритм выполняет следующие действия: 1) делит фрагмент на две равные (или почти равные) части; 2) каждую из частей (размером не более k элементов) рекурсивно упорядочивает; 3) выполняет слияние двух упорядоченных частей в один фрагмент массива Y; 4) копирует фрагмент массива Y в массив X на место исходного фрагмента. ________________________________________________ Вызов процедуры sort для упорядочения массива X из n элементов (при использовании вспомогательного массива Y) должен быть таким: sort(X,Y,1,n); ___________________________________ Если размер n упорядочиваемого массива 2m – 1 < n ≤ 2m, то не более чем за m последовательных делений размер фрагмента массива станет равным 1. Т.е. глубина рекурсии также не превысит m, т.е. log 2 n , что намного меньше размера массива X. _______________________________________________ Трудоемкость T (n): T (1) 1, T (n) 2T (n / 2) cn, где c – константа. n 2, 3, ..., Полагая 2m – 1 < n ≤ 2m, из (5.6) получим: T (n) = 2 T (n / 2) + c n = = 22 T (n / 4) + c n + c n = … ≤ c n m = = c n log 2 n . Рекуррентное соотношение для количества рекурсивных вызовов Rn процедуры sort при n = 2m: R1 1, 2 Rn 2 Rn / 2 1, n 2, 2 , ... Отсюда получим: Rn = 2Rn/2 + 1 = 4Rn/2 + 2Rn/2 + 1 = . . . = 2∙2m – 1 ≈ 2 n. В целом трудоемкость T (n) имеет порядок: O(n log n). Функции трудоемкости n 4 10 20 100 1000 106 log 2 n n log 2 n 2 4 5 7 10 20 8 40 100 700 10000 20∙106 n2 n3 2n 16 64 16 100 1000 1024 400 8000 ≈106 10000 106 ≈1030 106 109 ≈10300 1012 1018 ≈10300000 Процедура sortlist – рекурсивный алгоритм сортировки списков методом слияния. p – указатель на начало входного списка. n – количество элементов в списке. Список перед выполнением процедуры неупорядочен, после выполнения –полностью упорядочен. __________________________________________ procedure sortlist(var p:pel;n:integer); var p1,p2:pel; k,i:integer; begin if n>1 then begin k:=n div 2; p1:=p; for i:=1 to k-1 do p1:=p1^.p; p2:=p1^.p; p1^.p:=nil; p1:=p; {список разделен на две почти одинаковые части} sortlist(p1,k); sortlist(p2,n-k); {обе части списка рекурсивно отсортированы} slist(p1,p2,p) {обе части списка объединены слиянием} end end; Трудоемкость алгоритма тоже имеет порядок O(n log n).