-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpromotion-sort.cpp
More file actions
345 lines (277 loc) · 14.7 KB
/
promotion-sort.cpp
File metadata and controls
345 lines (277 loc) · 14.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
//Nested array sort with promotion (promotion sort)
#include <cstring> //memmove
#include <cmath>
/****************************************************************
PROMOTION SORT************************************************
*****************************************************************
EXPLANATION
-Promotion sort is originally based on nested array sort. The same simple idea holds: keep move operations to a minimum by making small arrays where each
element points to a child array that holds the elements between it and the next element in the parent.
Example:
{2} - First element
{23} - Second element 3 is greater than all elements in the array, put it at the end
{023} - Third element 0 is less than all elements in the array, put it at the beginning
{0!23} 0:{1} -Fourth element 1 is placed in a nested array associated with the smaller of the two elements it falls between
-Promotion sort seeks to solve the problem of nested array sort where randomness caused elements to have nested arrays of vastly varying sizes. This meant
that an element in the parent nest with few/no elements might have to be moved multiple times when it could have been an element in a child array
where it would have been hardly moved. Elements in the parent, ideally, should have larger nested arrays than elements in further nest levels,
and should be evenly sized.
-Promotion solves this by constructing the parent array from children that get "promoted". When an element gets promoted, it gets placed into the parent
array, and is assigned a nested array that holds half the elements from the array it came from. In other words, when a nested array gets too large
it is split in half, half of it remaining where it is and half of it being assigned to a promoted element. The promoted element, therefor, is the median
of the previously full array.
USEFULNESS
-Promotion is a somewhat expensive operation that pays dividends in the long run.
It happens relatively infrequently (N / array-size times, so at worst O(N) time), but ensures that each placement happens in estimated logarithmic time.
This results in a very fast, consistently fast sorting method with a reasonable use of memory. My very rough measurements were 2-3 times slower than
quick sort on a list of 20000 numbers. This does not account for memory allocation/deallocation, because if sorting more than just once the same
memory chunk can be reused. If memory allocation/deallocation is included the run time is a little bit slower
-The type of data structure that is created seems very useful, and though I haven't learned about it I wouldn't be surprised if a data structure similar to
it is used. It allows for quick insertions, deletions and searches. It is like a linked list that is accessible anywhere along the list.
-Improvements can be made on this prototype, which I have done and explained in pyramid-sort
*****************************************************************/
//TODO:
//Find a way to calculate the optimal container sizes and max nest level for a given array length
//Move binary search to its own function
//Trigger promotion of an element when a container hits its end or 0 index, rather than when a container hits a certain capacity
//This would allow the removal of acceptableFirstIndex variable
//Allow a max container size that isn't even
//Create a new header to use in place of nested-array-sort.h, and change name of function from nestedArraySort
#include "nested-array-sort.h"
class Benchmarks;
elementContainer* promote(elementContainer* parentContainer, const int& valuePosition, const int& insertedValue, const bool& isHigherThanMedian,
Benchmarks* bm, elementContainer*& containerAssigner, element*& elementAssigner);
struct elementContainer{
element *array;
int firstElementIndex;
int lastElementIndex;
int nestLevel;
int acceptableFirstIndex;
};
struct element{
int val;
elementContainer *nestedContainer;
};
class Benchmarks {
public:
int maxElements; //Maximum amount of elements allowed in an element container
int halfMaxElements;
int dblMaxElements;
int maxNestLevel;
Benchmarks(int arrayLength){
halfMaxElements = log2(arrayLength);
maxElements = 2 * halfMaxElements; //Must be even
dblMaxElements = 4 * halfMaxElements;
maxNestLevel = log2(arrayLength) / floor(log2(halfMaxElements));
}
};
//Insert an element into the elementContainer
void insertElement(int value,elementContainer* destination, Benchmarks* const bm, elementContainer*& containerAssigner, element*& elementAssigner){
bool isHigherThanMedian;
int valuePosition; //The index of the greatest value less than or equal to the value to be inserted
int low;
int high;
int mid;
//Loop until the lowest nest level is reached
while(true){
low = destination->firstElementIndex;
high = destination->lastElementIndex;
mid = (low+high) / 2;
//Check to see if the value to insert is higher or lower than the median, while updating low / high value for binary search
if(value >= destination->array[mid].val){
isHigherThanMedian = true;
low = mid + 1;
}
else{
isHigherThanMedian = false;
high = mid - 1;
}
//binary search
while(low < high){
mid = (low + high) / 2;
if(value >= destination->array[mid].val)
low = mid + 1;
else
high = mid - 1;
}
//use high to compare and assign,
//(either low or high would work if used in all 3 appearances, but high is used to make the first element insertion trick work)
if(value >= destination->array[high].val)
valuePosition = high;
else
valuePosition = high - 1;
if(destination->nestLevel < bm->maxNestLevel){
//if the nested container is at max capacity, promote
if((destination->array[valuePosition].nestedContainer->lastElementIndex - destination->array[valuePosition].nestedContainer->firstElementIndex + 1)
== bm->maxElements){
destination = promote(destination, valuePosition, value, isHigherThanMedian, bm, containerAssigner, elementAssigner);
}
else{
destination = destination->array[valuePosition].nestedContainer;
}
continue;
}
else break;
}//End while loop
//if the value to be inserted is higher than the median then inserting it would move all elements greater than it 1 index higher.
//The opposite is done if the value is lower than the median (all elements less than or equal to the value to be inserted are moved an index lower)
if(isHigherThanMedian){
//if a move is necessary, move elements in the array over
if(destination->lastElementIndex - valuePosition > 0)
{
memmove(destination->array + valuePosition + 2,destination->array + valuePosition + 1,
sizeof(element) * (destination->lastElementIndex - valuePosition));
}
destination->array[valuePosition + 1].val = value;
++destination->lastElementIndex;
return;
}
else{
//move the elements in the array over, insert the new value, and return
memmove(destination->array + destination->firstElementIndex - 1,destination->array + destination->firstElementIndex,
sizeof(element) * (valuePosition - destination->firstElementIndex + 1));
destination->array[valuePosition].val = value;
--destination->firstElementIndex;
return;
}
}
//Split a nested container in half by promoting its middle element to the parent container and associating it with the second half of the nested array
elementContainer* promote(elementContainer* parentContainer, const int& valuePosition, const int& insertedValue, const bool& isHigherThanMedian,
Benchmarks* bm, elementContainer*& containerAssigner, element*& elementAssigner){
element *parentElement = parentContainer->array + valuePosition;
elementContainer* fullContainer = parentElement->nestedContainer;
element promotedElement = fullContainer->array[fullContainer->firstElementIndex + bm->halfMaxElements];
//if the values are in indexes too low:
//assign the parent element's old container to the promoted element's nested container
//set parent element to have a new nested container, and copy the first half of elements to that new container
//if the values are in indexes too high:
//assign the promoted element a new nested container. Copy the second half of the elements and assign it to the new container
//in both cases, after both the old parent element and the promoted element point to the correct nested container::
//Update the indexes of the old nested container to discard the elements that were copied over
//insert the new element into the parent container
if(fullContainer->firstElementIndex <= fullContainer->acceptableFirstIndex){
promotedElement.nestedContainer = fullContainer;
//Assign parentElement a new container
parentElement->nestedContainer = containerAssigner++;
parentElement->nestedContainer->array = elementAssigner;
elementAssigner += bm->dblMaxElements;
parentElement->nestedContainer->firstElementIndex = bm->maxElements;
parentElement->nestedContainer->lastElementIndex = bm->maxElements + bm->halfMaxElements - 1;
parentElement->nestedContainer->acceptableFirstIndex = bm->halfMaxElements;
parentElement->nestedContainer->nestLevel = fullContainer->nestLevel;
memcpy(parentElement->nestedContainer->array + bm->maxElements,
fullContainer->array + fullContainer->firstElementIndex,
sizeof(element) * bm->halfMaxElements);
//Update fullContainer->firstElementIndex to discard the elements that were copied to the new container
fullContainer->firstElementIndex += bm->halfMaxElements;
}
else{
//Assign promotedElement a new container
promotedElement.nestedContainer = containerAssigner++;
promotedElement.nestedContainer->array = elementAssigner;
elementAssigner += bm->dblMaxElements;
promotedElement.nestedContainer->firstElementIndex = bm->maxElements;
promotedElement.nestedContainer->lastElementIndex = bm->maxElements + bm->halfMaxElements - 1;
promotedElement.nestedContainer->acceptableFirstIndex = bm->halfMaxElements;
promotedElement.nestedContainer->nestLevel = fullContainer->nestLevel;
memcpy(promotedElement.nestedContainer->array + bm->maxElements,
fullContainer->array + fullContainer->firstElementIndex + bm->halfMaxElements,
sizeof(element) * bm->halfMaxElements);
//Update fullContainer->lastElementIndex to discard the elements that were copied to the new container
fullContainer->lastElementIndex -= bm->halfMaxElements;
}
//Insert promotedElement into the non-full parent container
if(isHigherThanMedian){
//move the elements in the array over and insert the new value
memmove(parentElement + 2,
parentElement + 1,
sizeof(element) * (parentContainer->lastElementIndex - valuePosition));
parentContainer->array[valuePosition + 1] = promotedElement;
++parentContainer->lastElementIndex;
}
else{
//move the elements in the array over and insert the new value
memmove(parentContainer->array + parentContainer->firstElementIndex - 1,
parentContainer->array + parentContainer->firstElementIndex,
sizeof(element) * (valuePosition - parentContainer->firstElementIndex + 1));
parentContainer->array[valuePosition] = promotedElement;
--parentContainer->firstElementIndex;
--parentElement; //The parent element was shifted, so decrement to point to the correct element
}
return (insertedValue >= promotedElement.val) ?
promotedElement.nestedContainer :
parentElement->nestedContainer;
}
//Extract the elements from their nested elementContainers and put them in the correct, sorted order into a standard array
void placeElementsInArray(elementContainer *source, int *array, const int& maxNestLevel, int& index){
for(int i = source -> firstElementIndex; i <= source -> lastElementIndex; ++i){
if(source->nestLevel == maxNestLevel){
array[index] = source->array[i].val;
++index;
}
if(source->nestLevel != maxNestLevel){
placeElementsInArray(source->array[i].nestedContainer, array, maxNestLevel, index);
}
}
}
//Allocates memory for allContainers and allElements based on the array length
void allocateMemory(int arrayLength, elementContainer*& allContainers, element*& allElements){
allContainers = new elementContainer[arrayLength];
allElements = new element[arrayLength * 8];
}
//Deallocates memory for allContainers and allElements
void deallocateMemory(elementContainer*& allContainers, element*& allElements){
delete[] allContainers;
delete[] allElements;
allContainers = nullptr;
allElements = nullptr;
}
void nestedArraySort(int* array, int arrayLength){
elementContainer* allContainers = nullptr;
element* allElements = nullptr;
allocateMemory(arrayLength,allContainers,allElements);
nestedArraySort(array, arrayLength, allContainers, allElements);
deallocateMemory(allContainers,allElements);
}
void nestedArraySort(int *array, int arrayLength, elementContainer* containerAssigner, element* elementAssigner){
if(arrayLength <= 1)
return;
Benchmarks bm(arrayLength);
//Assign parent elementContainer
elementContainer* parent = containerAssigner++;
//arrays of elements are sized at 2 times the array length, because the starting element is in the middle, and the length of the array can span
//n elements to the end (each element higher than the previous) or n elements to the beginning (each element lower than the previous)
parent->array = elementAssigner;
elementAssigner += bm.dblMaxElements; //Subsequent arrays will be assigned at elementAssigner so that two arrays never overlap
parent->array[bm.maxElements].val = -99999;//Add the initial element
//Assign container values
parent->firstElementIndex = bm.maxElements; //halfway between the beginning and end of parent->array
parent->lastElementIndex = bm.maxElements;
parent->acceptableFirstIndex = bm.halfMaxElements;
parent->nestLevel = 0;
//Assign the initial element containers
elementContainer* initAssigner = parent;
for(int i = 1;i <= bm.maxNestLevel;++i){
//Create nested container
initAssigner->array[bm.maxElements].nestedContainer = containerAssigner++;
//Move down to the nested container
initAssigner = initAssigner->array[bm.maxElements].nestedContainer;
initAssigner->array = elementAssigner;
elementAssigner += bm.dblMaxElements;
initAssigner->array[bm.maxElements].val = -99999;
//Assign remaining container values
initAssigner->firstElementIndex = bm.maxElements;
initAssigner->lastElementIndex = bm.maxElements;
initAssigner->acceptableFirstIndex = bm.halfMaxElements;
initAssigner->nestLevel = i;
}
//Trick to remove minimum value from the bottom nest level.
//When the first value gets inserted, it gets inserted at lastElementIndex + 1, the firstElementIndex, and lastElementIndex gets incremented and matches
--initAssigner->lastElementIndex;
initAssigner->array[initAssigner->lastElementIndex].val = -99999;
for(int i = 0;i < arrayLength;++i){
insertElement(array[i],parent,&bm,containerAssigner,elementAssigner);
}
int index = 0;
placeElementsInArray(parent, array, bm.maxNestLevel, index);
}