1) Złożoność pamięciową liczy się bardzo podobnie co złożoność obliczeniową z różnicą że liczysz wykorzystanie pamięci a nie mocy obliczeniowej. Ma to zastosowanie również w rekurencji.
2) Złożoność pamięciowa sortowania przez scalanie zależy od konkretnej implementacji, aczkolwiek zakładam że chodzi Ci o najbardziej intuicyjną implementację, mniej więcej wyglądającą jak ta:
void merge(int *array, int i, int j, int k, int l) {
int *tempArray = new int[l-i];
// ...
delete [] tempArray;
}
void mergeSort(int *array, int i, int j) {
if(j - i <= 1) {
return;
}
int mid = (i + j) / 2;
mergeSort(array, i, mid); // Tutaj używamy S(n/2) pamięci
mergeSort(array, mid, j); // Tutaj używamy S(n/2) pamięci
merge(array, i, mid, mid, j); // Tutaj używamy O(n) pamięci gdzie n = j-i
}
Można zauważyć iż dodatkową pamięć wykorzystujemy dopiero po wykonaniu sortowania częściowego więc złożoność pamięciową w tym przypadku można wyrazić przez ciąg:
S(n) = max{S(n/2), S(n/2), O(n)} + O(1)
S(1) = O(1)
Bierzemy maksimum a nie sumę tych wartości ponieważ po zakończeniu funkcji wywoływanych w środku sortowania cała używana przez nich pamięć jest uwalniana. Rozwiązując ciąg rekurencyjny otrzymujemy O(n)
3) Raczej bierze się pod uwagę ograniczenia dolne/górne niżeli same parametry z którymi funkcja została wywołana przy liczeniu złożoności. Tyczy się to zarówno złożoności obliczeniowej jak i pamięciowej.