Shell sort algorithm: shift or swap? - algorithm

I read that Shell algorithm is an improved version of insertion sort, but I also read online sometimes it is about shifting, and sometimes I read it is about swapping, which one is correct?
For example: [5 3 2 10 0]
If we take the gap to be 2, then we will compare first 5 and 2, as a first step then, the result will be:
[2 3 5 10 0] by swapping and [2 5 3 10 0] by shifting, which one is Shell algorithm?

If we take the gap to be 2, then we will compare first 5 and 2, as a first step then, the result will be: [2 3 5 10 0] by swapping and [2 5 3 10 0] by shifting, which one is Shell algorithm?
The main principle in Shell sort is that with the chosen gap we look at the data as a collection of interleaved, shorter arrays. Each of those shorter arrays has their first entry at an index less than gap. These shorter arrays are sorted independently. Once that is done, the gap is reduced.
In the example, there are two interleaved arrays, which we can picture like this:
interleaved array: 5 2 0
interleaved array: 3 10
The first algorithm would fit under Shell sort. But the second one does not sort the interleaved arrays independently, as such rotations (shifts) move values from one interleaved array to another:
interleaved array: 5 πŸ ” 2 0
⬊ ⬈
interleaved array: 3 10
...resulting in:
interleaved array: 2 3 0
interleaved array: 5 10
Unless other precautions are made, the second algorithm will not ensure a rotation improves the situation. For instance, if the input is [3 1 2 4] and gap is 2, then the comparison of 3 and 2 will lead to a rotation, and we get [2 3 1 4]. But now we still have two values in the first interleaved array that are not in order (2 is greater than 1).
Shifting?
Shifting does not occur like you depicted it (crossing multiple interleaved arrays), but within one interleaved array it is generally done, just like it is done in insertion sort. So to apply that to your example:
interleaved array: 5 2 0
interleaved array: 3 10
The value 2 is picked up and preceding values are shifted forward within the same interleaved array until the right slot is found for the picked up value. In this case only one value is shifted (5), which makes it a swap:
interleaved array: 2 5 0
interleaved array: 3 10
Now 0 is picked up, and two values are shifted (2 and 5):
interleaved array: 0 2 5
interleaved array: 3 10
Now the first interleaved array is sorted. The second interleaved array happens to be sorted already. Then the gap is reduced to 1:
array: 0 3 2 10 5
Here 2 is picked up and one value (3) is shifted:
array: 0 2 3 10 5
Finally 5 is picked up and one value (10) is shifted:
array: 0 2 3 5 10

Related

Practical algorithms for permuting external memory

On a spinning disk, I have N records that I want to permute. In RAM, I have an array of N indices that contain the desired permutation. I also have enough RAM to hold n records at a time. What algorithm can I use to execute the permutation on disk as quickly as possible, taking into account the fact that sequential disk access is a lot faster?
I have plenty of excess disk to use for intermediate files, if desired.
This is a known problem. Find the cycles in your permutation order. For instance, given five records to permute [1, 0, 3, 4, 2], you have cycles (0, 1) and (2, 3, 4). You do this by picking an unused starting position; follow the index pointers until you return to your starting point. The sequence of pointers describes a cycle.
You then permute the records with an internal temporary variable, one record long.
temp = disk[0]
disk[0] = disk[1]
disk[1] = temp
temp = disk[2]
disk[2] = disk[3]
disk[3] = disk[4]
disk[4] = temp
Note that you can also perform the permutation as you traverse the pointers. You will also need some method to recall which positions have already been permuted, such as clearing the permutation index (set it to -1).
Can you see how to generalize that?
This is an problem with interval coordination. I'll simplify the notation slightly by changing the memory available to M records -- having upper- and lower-case N is a little confusing.
First, we re-cast the permutations as a series of intervals, the rotational span during which a record needs to reside in RAM. If a record needs to be written to a lower-numbered position, we increase the endpoint by the list size, to indicate the wraparound -- have to wait for the next disk rotation. For instance, using my earlier example, we expand the list:
[1, 0, 3, 4, 2]
0 -> 1
1 -> 0+5
2 -> 3
3 -> 4
4 -> 2+5
Now, we apply standard greedy scheduling resolution. First, sort by endpoint:
[0, 1]
[2, 3]
[3, 4]
[1, 5]
[4, 7]
Now, apply the algorithm for M-1 "lanes"; the extra one is needed for swap space. We fill each lane, appending the interval with the earliest endpoint, whose start-point doesn't overlap:
[0, 1] [2, 3] [3, 4] [4, 7]
[1, 5]
We can do this in a total of 7 "ticks" if M >= 3. If M=2, we defer the second lane by 2 rotations to [11, 15].
Sneftal's nice example gives us more troubles, with deeper overlap:
[0, 4]
[1, 5]
[2, 6]
[3, 7]
[4, 0+8]
[5, 1+8]
[6, 2+8]
[7, 3+8]
This requires 4 "lanes" if available, deferring lanes as needed if M < 5.
The pathological case is where every record in the permutation needs to be copied back one position, such as [3, 0, 1, 2], with M=2.
[0, 3]
[1, 4]
[2, 5]
[3, 6]
In this case, we walk through the deferral cycle multiple times. At the end of every rotation, we have to defer all remaining intervals by one rotation, resulting in
[0, 3] [3, 6] [2+4, 5+4] [1+4+4, 4+4+4]
Does that get you moving, or do you need more detail?
I have an idea, which might need further improvement. But here it goes:
suppose the hdd has the following structure:
5 4 1 2 3
And we want to write out this permutation:
2 3 5 1 4
Since hdd is a circular buffer, and assuming it can only rotate in one direction, we can write the above permutation using shifts as such:
5 >> 2
4 >> 3
1 >> 1
2 >> 2
3 >> 2
So let's put that in an array, and since we know it is a circular array, lets put its mirrors side by side:
| 2 3 1 2 2 | 2 3 1 2 2| 2 3 1 2 2 | 2 3 1 2 2 |... Inf
Since we want to favor sequential reads, (or writes) we can put a cost function to the above series. Let the cost function be linear, i. e:
0 1 2 3 4 5 6 7 8 9 10 ... Inf
Now, let us add the cost function to the above series, but how to select the starting point?
The idea is to select the starting point such that you get the maximum congruent monotonically increasing sequence.
For example, if you select the 0 point to be on "3", you'll get
(1) | - 3 2 4 5 | 6 8 7 9 10 | ...
If you select the 0 point to be on "2", the one just right of "1", you'll get:
(2) | - - - 2 3 | 4 6 5 7 8 | ...
Since we are trying to favor consecutive reads, lets define our read-write function to work as such:
f():
At any currently pointed hdd location, function will read the currently pointed hdd file, into available RAM. (namely, total space - 1, because we want to save 1 for swap)
If no available space is left on RAM for read, the function will assert and program will halt.
At any current hdd location, if ram holds the value that we want to be written in that hdd location, function reads the current file into swap space, writes the wanted value from the ram to hdd, and destroys the value in ram.
If a value is placed into hdd, function will check if the sequence is completed. If it is, program will return with success.
Now, we should note that if the following holds:
shift amount <= n - 1 (n : available memory we can hold)
We can traverse the hard disk in once pass using the above function. For example:
current: 4 5 6 7 0 1 2 3
we want: 0 1 2 3 4 5 6 7
n : 5
We can start anywhere we want, say from the initial "4". We read 4 items sequentially, (n has 4 items now) and we start placing from 0 1 2 3, (we can because n = 5 total, and 4 is used. 1 is used for swap). So the total operations is 4 consecutive reads, and then r-w operations for 8 times.
Using that analogy, it becomes clear that if we subtract "n-1" from equations (1) and (2), the positions which have value "<= 0" will be a better suit for initial position because the ones higher than zero will definitely require another pass.
So we select eq. (2) and subtract, for let's say "n = 3", we subtract 2 from eq. (2):
(2) | - - - 0 1 | 2 4 3 5 6 | ...
Now it is clear that, using f(), and starting from 0, assuming n = 3, we will have a starting operation as such: r, r, r-w, r-w, ...
So, how do we do the rest and find minimum cost? We will place an array with initial minimum cost, just below equation (2). The positions in that array will signify where we want f() to be executed.
| - - - 0 1 | 2 4 3 5 6 | ...
| - - - 1 1 | 1 1 1 1 1 | ...
The second array, the ones with 1's and 0's tell the program where to execute f(). Note that, if we assumed those locations wrong, f() will assert.
Before we start actually placing files into hdd, we of course want to see if the f() positions are correct. We check if there are assertions, we we will try to minimize cost whilst removing all assertions. So, e.g:
(1) 1111000000000000001111
(2) 1111111000000000000000
(1) obviously has higher cost that (2). So the question simplifies on finding the 1-0 array.
Some ideas on finding the best array:
Simplest solution is to write out all 1's and turn assertions into 0's. (essentially it's a skip). This method is guaranteed to work.
Brute force: write an array of as shown in (2) and start shifting 1's to right, in such an order that tries out every permutation available:
1111111100000000
1111111010000000
1111110110000000
...
Full random approach: Plug in mt1997 and start permuting. Whenever you see a sharp drop in cost, stop executing and implement hdd copy-paste. You won't find the global minimum, but you'll get a nice trade-off.
Genetic algorithms: For permutations where "shift count is much lower than n - 1", the methodology provided in this answer should (?) provide a global minimum and smooth gradients. This allows one to use genetic algorithms without relying on mutations too much.
One advantage I find in this approach is that, since OP mentioned that this is a real life problem, the method provides an easy(ier?) way to change cost functions. It is easier to detect the effect of say, having lots of contigous small files to be copied vs. having a single huge file. Or perhaps rrwwrrww is better than rrrrwwww?
Does any of this even make sense? We will have to try out ...

Algorithm to swap two indices in a symmetric matrix

I am trying for a day now to find an algorithm to swap two indices in a symmetric matrix so that the result is also a symmetric matrix.
LetΒ΄s say I have following matrix:
0 1 2 3
1 0 4 5
2 4 0 6
3 5 6 0
LetΒ΄s say I want to swap line 1 and line 3 (where line 0 is the first line). Just swapping results in:
0 1 2 3
3 5 6 0
2 4 0 6
1 0 4 5
But this matrix is not symmetric anymore. What I really want is following matrix as a result:
0 3 2 1
3 0 6 5
2 6 0 4
1 5 4 0
But I am not able to find a suitable algorithm. And that really cracks me up, because it looks like in easy task.
Does anybody know?
UPDATE
Phylogenesis gave a really simple answer and I feel silly that I could not think of it myself. But here is a follow-up task:
LetΒ΄s say I store this matrix as a two-dimensional array. And to save memory I do not save the redundant values and I also leave out the diagonal which has always 0 values. My array looks like that:
[ [1, 2, 3], [4, 5], [6] ]
My goal is to transform that array to:
[ [3, 2, 1], [6, 5], [4] ]
How can I swap the rows and then the columns in an efficient way using the given array?
It is simple!
As you are currently doing, swap row 1 with row 3. Then swap column 1 with column 3.

Ascending Cardinal Numbers in APL

In the FinnAPL Idiom Library, the 19th item is described as β€œAscending cardinal numbers (ranking, all different) ,” and the code is as follows:
⍋⍋X
I also found a book review of the same library by R. Peschi, in which he said, β€œ'Ascending cardinal numbers (ranking, all different)' How many of us understand why grading the result of Grade Up has that effect?” That's my question too. I searched extensively on the internet and came up with zilch.
Ascending Cardinal Numbers
For the sake of shorthand, I'll call that little code snippet β€œrank.” It becomes evident what is happening with rank when you start applying it to binary numbers. For example:
X←0 0 1 0 1
⍋⍋X ⍝ output is 1 2 4 3 5
The output indicates the position of the values after sorting. You can see from the output that the two 1s will end up in the last two slots, 4 and 5, and the 0s will end up at positions 1, 2 and 3. Thus, it is assigning rank to each value of the vector. Compare that to grade up:
X←7 8 9 6
⍋X ⍝ output is 4 1 2 3
⍋⍋X ⍝ output is 2 3 4 1
You can think of grade up as this position gets that number and, you can think of rank as this number gets that position:
7 8 9 6 ⍝ values of X
4 1 2 3 ⍝ position 1 gets the number at 4 (6)
⍝ position 2 gets the number at 1 (7) etc.
2 3 4 1 ⍝ 1st number (7) gets the position 2
⍝ 2nd number (8) gets the position 3 etc.
It's interesting to note that grade up and rank are like two sides of the same coin in that you can alternate between the two. In other words, we have the following identities:
⍋X = ⍋⍋⍋X = ⍋⍋⍋⍋⍋X = ...
⍋⍋X = ⍋⍋⍋⍋X = ⍋⍋⍋⍋⍋⍋X = ...
Why?
So far that doesn't really answer Mr Peschi's question as to why it has this effect. If you think in terms of key-value pairs, the answer lies in the fact that the original keys are a set of ascending cardinal numbers: 1 2 3 4. After applying grade up, a new vector is created, whose values are the original keys rearranged as they would be after a sort: 4 1 2 3. Applying grade up a second time is about restoring the original keys to a sequence of ascending cardinal numbers again. However, the values of this third vector aren't the ascending cardinal numbers themselves. Rather they correspond to the keys of the second vector.
It's kind of hard to understand since it's a reference to a reference, but the values of the third vector are referencing the orginal set of numbers as they occurred in their original positions:
7 8 9 6
2 3 4 1
In the example, 2 is referencing 7 from 7's original position. Since the value 2 also corresponds to the key of the second vector, which in turn is the second position, the final message is that after the sort, 7 will be in position 2. 8 will be in position 3, 9 in 4 and 6 in the 1st position.
Ranking and Shareable
In the FinnAPL Idiom Library, the 2nd item is described as β€œAscending cardinal numbers (ranking, shareable) ,” and the code is as follows:
⌊.5Γ—(⍋⍋X)+βŒ½β‹β‹βŒ½X
The output of this code is the same as its brother, ascending cardinal numbers (ranking, all different) as long as all the values of the input vector are different. However, the shareable version doesn't assign new values for those that are equal:
X←0 0 1 0 1
⌊.5Γ—(⍋⍋X)+βŒ½β‹β‹βŒ½X ⍝ output is 2 2 4 2 4
The values of the output should generally be interpreted as relative, i.e. The 2s have a relatively lower rank than the 4s, so they will appear first in the array.

Divide Set of numbers to sequence? Find General Term?

How can we divide set of numbers to sequence? And find the general term?
1 - numbers are always in order
2 - if we have n numbers n/2 numbers are always present
For example we have:
Input: 0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30
Output--> 2*X, x=[0..15]
OR
Input: 0,2,4,5,6,8,10,12,14,15,16,18,20,22,24,26,28,30
Divide into two set
A: 0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30
B: 5,10,15,20
Output--> 2*X, x=[0..15] AND 5*X, x=[1..4]
I think this is very difficult, any comments?
What computer field or algorithm can help me?
The problem as I understand it is this: Given a sequence of numbers, find the set of sequences that start from zero and increase by a constant multiple which cover this set.
Here's a general outline of what I would do:
I would make a list of all the numbers in the set, and iterate through starting from the first two elements to generate all of the possible sets meeting your criteria which are here. If you encounter an element in the list, you can remove it from consideration as a generating number since any list with that number as a constant multiple is a subset of a list you've encountered before. When you are done you will have a list of possible sets you can use to cover that set. FOR EXAMPLE:
0,2,4,5,6,8,10,12,14,15,16,18,20,22,24,26,28,30
We will start with 0 and 2. We'll look for elements that are successively 2 larger and remove them from the list of elements that will be considered as possible multiples. Once we find a multiple of 2 that's not in this list, we'll stop generating. Here we go:
s(2) = [0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30]
Which leaves:
[5,15]
as the two potential other candidates. Do you see that any of the elements, eg, 4, which are divisible by two will make subsets of that list and thus don't need to be considered?
The remaining list in the set will start at 0 and increase by 5, our smallest element:
[0,5,10,15,20]
(Remember we are checking the original list for these multiples and not the truncated list- the truncated list is only the list of remaining candidates. When the candidate list is empty we know we will have found all of the sets which are contained in this set who have no supersets.
For a more complex example:
[0 2 3 4 5 6 7 8 9 10 12 13 14 15]
We'll start with:
[0 2 4 6 8 10 12 14]
Which leaves
[3 5 7 9 13 15]
as candidates, which in turn generates:
[0 3 6 9 12 15]
which leaves
[5 7 13]
which generates
[0 5 10 15]
which leaves
[7 13]
which generates
[0 7 14]
which leaves
[13]
which generates
[0 13].
The total combination of sets is:
[0 2 4 6 8 10 12 14]
[0 3 6 9 12 15]
[0 5 10 15]
[0 7 14]
[0 13].
At this point, you have the smallest list of all of the sets needed to cover your set. It should be trivial to generate the proper [0,1...n]/a*n descriptors from here.

Permutation of a vector

suppose I have a vector:
0 1 2 3 4 5
[45,89,22,31,23,76]
And a permutation of its indices:
[5,3,2,1,0,4]
Is there an efficient way to resort it according to the permutation thus obtaining:
[76,31,22,89,45,23]
Using at most O(1) additional space?
Yes. Starting from the leftmost position, we put the element there in its correct position i by swapping it with the (other) misplaced element at that position i. This is where we need the O(1) additional space. We keep swapping pairs of elements around until the element in this position is correct. Only then do we proceed to the next position and do the same thing.
Example:
[5 3 2 1 0 4] initial state
[4 3 2 1 0 5] swapped (5,4), 5 is now in the correct position, but 4 is still wrong
[0 3 2 1 4 5] swapped (4,0), now both 4 and 0 are in the correct positions, move on to next position
[0 1 2 3 4 5] swapped (3,1), now 1 and 3 are both in the correct positions, move on to next position
[0 1 2 3 4 5] all elements are in the correct positions, end.
Note:
Since each swap operation puts at least one (of the two) elements in the correct position, we need no more than N such swaps altogether.
Zach's solution is very good.
Still, I was wondering why there is any need to sort. If you have the permutation of the indices, use the values as a pointer to the old array.
This may eliminate the need to sort the array in the first place. This is not a solution that can be used in all cases, but it will work fine in most cases.
For example:
a = [45,89,22,31,23,76];
b = [5,3,2,1,0,4]
Now if you want to lop through the values in a, you can do something like (pseudo-code):
for i=0 to 4
{
process(a[i]);
}
If you want to loop through the values in the new order, do:
for i=0 to 4
{
process(a[b[i]]);
}
As mentioned earlier, this soluion may be sufficient in many cases, but may not in some other cases. For other cases you can use the solution by Zach.But for the cases where this solution can be used, it is better because no sorting is needed at all.

Resources