algorithm visualization: implementing insertion sort without loops but with variable increments everytime a function is called

huangapple go评论83阅读模式
英文:

algorithm visualization: implementing insertion sort without loops but with variable increments everytime a function is called

问题

我在Processing中正在构建一个排序算法的可视化工具(使用Java的扩展库进行可视化),我遇到了一个问题,我认为其他人可能能够帮助我解决。在Processing中有一个名为draw()的函数,每秒调用60次。我想要在每次调用draw()时执行插入排序算法的一步。我已经使用冒泡排序实现了它(请参见下面的代码)。updateBubble()draw()中被调用,'colors'是我用来存储要排序的不同颜色值的ArrayList的名称。

这是BubbleSort类中的函数(bubble是这个类的一个对象):

void sort(int j) {
    if (j < colors.size() - 1) {
        if (colors.get(j) > colors.get(j+1)) { 
            int temp = colors.get(j); 
            colors.set(j, colors.get(j+1)); 
            colors.set((j+1), temp);
        } 
    }
}

这种方法使我能够减慢可视化过程的速度,使其与帧速率的速度相匹配,而我可以自己控制帧速率,而不使用会立即执行排序算法的循环。现在我也想为插入排序算法创建类似的实现,但我感到困惑,因为我似乎无法使用类似的实现,或者可能有更好的方法?

目前我的代码会立即执行它,而不允许我看到过程。

void updateInsertion() {
    insertion.sort();
}

这是我现在的代码,仍然是错误的,但越来越接近了我的目标,制作一个仅使用递增和if语句而不是while和for循环的函数,以便每次调用方法时都会执行不同的步骤。

void updateInsertion() {
    // i表示for循环变量
    if (i < insertion.colors.size()) {
        if (j < 0 || insertion.colors.get(j) <= insertion.colors.get(i)) {
            insertion.colors.set(j+1, keap);
            if (notSortedYet()) {
                i++;
                keap = insertion.colors.get(i);
                j = i - 1;
            }
        } else {
            insertion.colors.set((j+1), insertion.colors.get(j));
            j = j - 1;
        }
    }
}

编辑:我已经修复了它,你可以在下面找到我的解决方案:每次调用updateInsertion()时,我的代码将执行算法中的确切一步!感谢所有付出努力的人评论,我不知道这是否是最佳做法,所以如果你愿意,随时告诉我有关这方面的最新信息!

void updateInsertion() {
    // i表示for循环变量
    if (i < insertion.colors.size()) {
        if (j >= 0 && insertion.colors.get(j) > firstUnsorted) {
            int temp = insertion.colors.get(j+1);
            insertion.colors.set((j+1), insertion.colors.get(j));
            insertion.colors.set(j, temp);
            j = j - 1;
        } else {
            insertion.colors.set(j+1, firstUnsorted);
            if (i < insertion.colors.size() - 1) {
                i++;
            }
            firstUnsorted = insertion.colors.get(i);
            j = i - 1;
        }
    }
}
英文:

I'm building a sorting algorithm visualizer in processing (extension of java with extra libraries for visualization) and i'm very much stuck on this problem which I think others will be able to help me solve.
In processing there is a function called draw() that is being called 60 times each second. It's here that I want to execute, each time draw() is called, one step of the insertion algorithm. I already implemented it with a bubble sort. (see code below).
updateBubble() is being called in draw() and 'colors' is the name of the arraylist I use to keep the different values of colors to sort.

picture to get a better understanding:
[![visualisation algorithm preview][1]][1]

...
int j = 0
...
void updateBubble() {
  bubble.sort(j);
  j++;
  if (i&lt;bubble.colors.size()) {
    if (j &gt;= bubble.colors.size()-i-1) {
      j = 0;
      i++;
    }
  } else {
    bubble.sorted = true;
  }
}

and here is the function in the class BubbleSort (bubble is an object of this class)

void sort(int j) {
    if (j&lt;colors.size()-1) {
      if (colors.get(j) &gt; colors.get(j+1)) 
      { 
        int temp = colors.get(j); 
        colors.set(j, colors.get(j+1)); 
        colors.set((j+1), temp);
      } 
    }
  }

This way I was able to slow down the visualization process to the the pace of the framerate which I can control myself without using loops which would execute the sorting algorithm immediately. Now I also wanted to make a similar implementation for the insertion sort algorithm but i feel like i'm stuck because I don't seem to be able to use a similar implementation that works or there might be a better way to do this?
What I have at the moment executes it immediately as expected, without being able to see the process.

void updateInsertion() {
  insertion.sort();
}
void sort() {
    int n = colors.size(); 
    for (int i = 1; i &lt; n; ++i) { 
      int key = colors.get(i); 
      int j = i - 1; 
      while (j &gt;= 0 &amp;&amp; colors.get(j) &gt; key) { 
        colors.set(j+1, colors.get(j));
        j = j - 1;
      } 
      colors.set(j+1, key);
    }
  }

this is what i got now: which is still wrong but is getting closer and clearifies what i'm trying to reach, making a function that only works with increments and if statements instead of whiles and fors so each different step is being executed with each call of the method.

  // i resembles for loop variable
  if (i&lt;insertion.colors.size()) {
    if (j&lt;0 || insertion.colors.get(j) &lt;= insertion.colors.get(i)) { // negative check to go out of while loop
      insertion.colors.set(j+1, keap);
      if(notSortedYet()){
      i++;
      keap = insertion.colors.get(i);
      j = i - 1;
      }
    } else { // resembles being in the while loop
      insertion.colors.set((j+1), insertion.colors.get(j));
      j = j - 1;
    }
  }
}                                                                                                                                       

EDIT: I fixed it and you can find my solution beneath algorithm visualization: implementing insertion sort without loops but with variable increments everytime a function is called everytime updateInsertion() is called, my code will execute exact one step in the algorithm! thanks to everyone who put effort into commenting, I dont know if this is best practise, so keep me updated on that if you want!

void updateInsertion() {

  // i resembles for loop variable

  if (i&lt;insertion.colors.size()) {
    if (j&gt;=0 &amp;&amp; insertion.colors.get(j) &gt; firstUnsorted) {
      int temp = insertion.colors.get(j+1);
      insertion.colors.set((j+1), insertion.colors.get(j));
      insertion.colors.set(j,temp);
      j = j - 1;
    } else {
      insertion.colors.set(j+1, firstUnsorted);
      if (i&lt;insertion.colors.size()-1) {
        i++;
      }
      firstUnsorted = insertion.colors.get(i);
      j = i - 1;
    }
  }
}

答案1

得分: 1

以下是翻译好的部分:

我喜欢这个项目。

Processing还有一个millis()方法,可以返回自从您开始绘制草图以来经过的毫秒数。我有时会用它来计时我的动画,这可能在这里很有用。下面是一个计时器类的实现:

class Delay {
  int limit;
  
  Delay (int l) {
    limit = millis() + l;
  }
  
  boolean expired () {    
    return (millis() > limit);
  }
}

我建议您在这里使用这个类,而不是调整帧速率。通过使用延迟来减慢排序的实现,您可以让计算机按照自己的节奏工作,并且仅在需要绘制新帧时才绘制。像这样(请原谅我在其中说“做些事情”的部分):

Delay holdTheFrame = new Delay(1);
void draw() {
  if(holdTheFrame.expired()) {
    holdTheFrame = new Delay(500); // 下一帧前半秒
    // 在排序中向前推进一步
    // 绘制数据的可视化
  }
}

您可以调整数据排序的速度,仅在数据发生变化时才进行绘制。这是双赢的策略!

玩得开心!

编辑:

为了帮助您实现,这是一个示例。您可以将此代码复制并粘贴到空的Processing草图中,它将按原样运行。为了使我的工作更加简化,我会将内容打印到控制台,而不是使用图形显示,但您应该能够理解我在做什么。

这里的关键是,我的排序算法已经在细微的地方进行了修改,因此每次调用它们时都只会运行一个排序步骤。自己看看:

int _numberOfItems = 10;
int _sortingStep = 0;
IntList _bubbleList = new IntList();
boolean _bubbleListSorted = false;
IntList _selectionList = new IntList();
IntList _insertionList = new IntList();
Delay _delay = new Delay(1);

void setup() {  
  // 初始化数据列表
}

void draw() {
  if (_delay.expired()) {
    _delay = new Delay(500);

    // 对每个算法进行单步排序
    if (!_bubbleListSorted) {
      singleStepBubbleSort(_bubbleList);
    }
    if (_sortingStep < _numberOfItems) {
      singleStepSelectionSort(_selectionList, _sortingStep);
      singleStepInsertionSort(_insertionList, _sortingStep);
    }
    _sortingStep++;

    // 更新显示(为了简单起见,我在控制台上打印)
    // ...
  }
}

// 插入排序的单步实现
void singleStepInsertionSort(IntList list, int step) {
  // ...
}

// 冒泡排序的单步实现
void singleStepBubbleSort(IntList list) {
  // ...
}

// 选择排序的单步实现
void singleStepSelectionSort(IntList list, int step) {
  // ...
}

class Delay {
  // ...
}

如果您有疑问,请随时告诉我。

英文:

I love this project.

Processing also have a millis() method which returns how many milliseconds were spent since you've started your sketch. I sometimes use it to time my animations, which could come in handy right here. Here's an implementation of a timer class:

class Delay {
  int limit;
  
  Delay (int l) {
    limit = millis() + l;
  }
  
  boolean expired () {    
    return (millis() &gt; limit);
  }
}

I suggest that you use this class instead of tweaking the FPS. By using the Delay to slow down your implementation of the sort, you're letting the computer work at it's own rhythm and only draw a new frame when you need it. Like this (excuse the parts where I say "do stuff"):

Delay holdTheFrame = new Delay(1);
void draw() {
  if(holdTheFrame.expired()) {
    holdTheFrame = new Delay(500); // half a second before the next frame
    // Advance one step forward in your sorting
    // Draw the visualization of the data
  }
}

You can fine tune at what pace your data is sorted and only paint it when it changes. It's win-win!

Have fun!


EDIT

To help you with the implementation, here's an example one. You can copy and paste this code in an empty Processing sketch and it'll run as-is. To make things easier on my side I print to console instead of using the graphical display, but you should be able to get what I'm doing.

The secret here is that my sorting algorithm have been subtly modified so they instead always run only ONE sorting step when I call them. See for yourself:

int _numberOfItems = 10;
int _sortingStep = 0;
IntList _bubbleList = new IntList();
boolean _bubbleListSorted = false;
IntList _selectionList = new IntList();
IntList _insertionList = new IntList();
Delay _delay = new Delay(1);

void setup() {  
  for (int i=0; i&lt;_numberOfItems; i++) {
    _bubbleList.append((int)random(10, 99));
  }
  for (int i=0; i&lt;_numberOfItems; i++) {
    _selectionList.append((int)random(10, 99));
  }
  for (int i=0; i&lt;_numberOfItems; i++) {
    _insertionList.append((int)random(10, 99));
  }
}

void draw() {
  if (_delay.expired()) {
    _delay = new Delay(500);

    // sort one step with every algo you want to display
    if (!_bubbleListSorted) {
      singleStepBubbleSort(_bubbleList);
    }
    if (_sortingStep &lt; _numberOfItems) {
      singleStepSelectionSort(_selectionList, _sortingStep);
      singleStepInsertionSort(_insertionList, _sortingStep);
    }
    _sortingStep++;

    // update the display (I&#39;m printing to console instead for simplicity)
    for (int i : _bubbleList) {
      print(i + &quot; &quot;);
    }
    print(&quot;  |  &quot;);
    for (int i : _selectionList) {
      print(i + &quot; &quot;);
    }
    print(&quot;  |  &quot;);
    for (int i : _insertionList) {
      print(i + &quot; &quot;);
    }
    print(&quot;\n&quot;);
  }
}

// An &quot;single-step&quot; implementation of Insertion Sort
void singleStepInsertionSort(IntList list, int step) {
  int k = list.get(step); 
  int j = step - 1; 
  while (j &gt;= 0 &amp;&amp; list.get(j) &gt; k) { 
    list.set(j+1, list.get(j));
    j = j - 1;
  } 
  list.set(j+1, k);
}

// An &quot;single-step&quot; implementation of Bubble Sort
void singleStepBubbleSort(IntList list) { 
  int temp; 
  boolean swapped = false;

  for (int i=0; i&lt;list.size()-1; i++)  
  { 
    if (list.get(i) &gt; list.get(i + 1))  
    { 
      // swap arr[j] and arr[j+1] 
      temp = list.get(i); 
      list.set(i, list.get(i+1)); 
      list.set(i+1, temp); 
      swapped = true;
    }
  }

  if (!swapped) {
    _bubbleListSorted = true;
  }
}

// An &quot;single-step&quot; implementation of Selection Sort
void singleStepSelectionSort(IntList list, int step) 
{ 
  int min_idx = step; 
  for (int j = step+1; j &lt; list.size(); j++) {
    if (list.get(j) &lt; list.get(min_idx)) {
      min_idx = j;
    }
  }

  int temp = list.get(min_idx); 
  list.set(min_idx, list.get(step)); 
  list.set(step, temp);
}

class Delay {
  int limit;

  Delay (int l) {
    limit = millis() + l;
  }

  boolean expired () {    
    return (millis() &gt; limit);
  }
}

Let me know if you have questions.


MORE EDITS:

Every swap of an insertion sort means many, many swaps. It's a real pain because this algorithm is kinda complicated to stop in it's tracks.

Luckily, I don't care. Thinking outside the box, I opted instead to create a class dedicated to sort an array while recording how to sort it, then be able to play it back "as if it was happening in real time". take a look:

int numberOfItems = 10;
int sortingStep = 0;
Delay delay = new Delay(1);
ManagedSelectionSort managedSelectionSort;  // I created a class just to manage this madness

void setup() {
  IntList list = new IntList();
  for (int i=0; i&lt;numberOfItems; i++) {
    list.append((int)random(10, 99));  // some random numbers to sort later 
  }

  managedSelectionSort = new ManagedSelectionSort(list);  // take a look at the instantiation of this class

  print(&quot;Step &quot; + String.format(&quot;%02d&quot;, sortingStep) + &quot;: &quot;);
  printArray(managedSelectionSort.list);
  print(&quot;\n&quot;);
}

void draw() {
  if (delay.expired()) {    
    delay = new Delay(100);  // i put a very short delay, you&#39;ll probably want to tweak this

    managedSelectionSort.sortOneStep();  // this is not what it seems
    sortingStep++;

    print(&quot;Step &quot; + String.format(&quot;%02d&quot;, sortingStep) + &quot;: &quot;);
    printArray(managedSelectionSort.list);
    print(&quot;\n&quot;);
  }
}

// this class is where the magic happens
// we&#39;ll sort the array all at once while recording every move
// then we&#39;ll play back those moves on a copy of the array
class ManagedSelectionSort {
  IntList list, hiddenList;  // list is the &quot;official&quot; list, while hiddenList is where the heavy lifting happens
  ArrayList&lt;SwapIndex&gt; swapList;  // this is where I record how to sort the array

  ManagedSelectionSort(IntList baseList) {  // this way I can instantiate several similar objects with the same list
    list = new IntList();
    hiddenList = new IntList();
    swapList = new ArrayList&lt;SwapIndex&gt;();

    for (int i : baseList) {
      // both lists have the same initial numbers
      list.append(i);
      hiddenList.append(i);
    }

    // as soon as this object is instantiated, it knows how it&#39;ll sort the array
    // because it already did...
    hiddenSort();
  }

  // this method plays the moves which were recorded earlier according to the current sortingStep
  // the swapList array was filled with every swap needed to sort the array, one by one
  // now it&#39;s just a matter of playing them back on a copy of the initial array
  void sortOneStep() {
    if (sortingStep &lt; swapList.size()) {
      swap(list, swapList.get(sortingStep).index1, swapList.get(sortingStep).index2);
    }
  }

  // this is the real implementation of the insertion sort
  void hiddenSort() 
  {
    for (int i=1; i&lt;hiddenList.size(); i++) {
      int j = i;

      while (j&gt;0 &amp;&amp; hiddenList.get(j) &lt; hiddenList.get(j-1)) {
        swap(hiddenList, j, j-1, true);  // swap is a class specific helper method, it swaps the numbers and also records the move
        j--;
      }
    }
  }

  // this is an overload, i could have done without but it&#39;s confortable
  void swap(IntList list, int index1, int index2) {
    swap(list, index1, index2, false);
  }
  void swap(IntList list, int index1, int index2, boolean recordMove) {
    // the swap first
    int temp = list.get(index1);
    list.set(index1, list.get(index2));
    list.set(index2, temp);

    // if the method is set on &#39;record&#39;, it adds this move to the swapList array
    if (recordMove) {      
      swapList.add(new SwapIndex(index1, index2));
    }
  }
}

// this class could have been a struct, but I like to start in OOP right from the bat in case things gets complicated
class SwapIndex {
  int index1;
  int index2;

  SwapIndex(int index1, int index2) {
    this.index1 = index1;
    this.index2 = index2;
  }
}

// this method is just an helper method to print to console
void printArray(IntList list) {
  for (int i : list) {
    print(i + &quot; &quot;);
  }
}

class Delay {
  int limit;

  Delay (int l) {
    limit = millis() + l;
  }

  boolean expired () {    
    return millis() &gt; limit;
  }
}

This should solve your initial problem, if I understood it right this time!

答案2

得分: 0

这可以通过一种存储状态的方式来实现。以下是我所讨论的大致内容。

    // 启动过程。必须在draw()之前调用。
    void init() {
        state = "forLoop";

        i = 1;
        n = colors.size();
    }

    // 循环的单次迭代。
    void draw(){
        switch(state) {
            case "forLoop":
                doForLoopBody();
                break;
            case "whileLoop":
                doWhileLoopBody();
                break;
            ...
        }
    }

    // 执行while循环中的所有内容以及紧随其后的一两个操作。
    void doWhileLoopBody() {
        if (isThisIterationOfWhileDone()) {
            // 退出while循环,准备进行下一次for循环的迭代。
            // 在下面的几行中更好的方法是引入一个附加状态(例如:“postWhile”),
            // 它将在此方法之后执行,并处理colors.set()、增加i等操作。
            state = "forLoop";
            colors.set(j+1, key);
            i++;
            return;
        }
        // 更新颜色,更新j的值等...
    }

    // 执行while循环之前的所有内容。
    void doForLoopBody() {
        if (isThisIterationOfForDone()) {
            state = "END";
            return;
        }

        // 更新颜色,获取key和j的初始值,等等

        // 切换到处理while循环的主体
        state = "whileLoop";
    }

请注意,代码中的注释以及函数名并未被翻译,因为它们是代码的一部分,与翻译无关。

英文:

One way to achieve this is via a some sort of stored state. Below is at a high level what I'm talking about.

// Starts the procedure. Must be called before draw().
void init() {
    state = &quot;forLoop&quot;;

    i = 1;
    n = colors.size();
}

// Single iteration of a loop.
void draw(){
    switch(state) {
        case &quot;forLoop&quot;:
            doForBody();
            break;
        case &quot;whileLoop&quot;:
            doWhileLoopBody();
            break;
        ...
    }
}

// Executes everything in the while loop and the one or two things
// just after it.
void doWhileLoopBody() {
    if (isThisIterationOfWhileDone()) {
        // Get out of the while loop and prepare for the next iteration of for.
        // A better way to what I&#39;m doing on the next couple lines here would
        // be to introduce an additional state (ex: &quot;postWhile&quot;) that would
        // execute just after this method and would handle the colors.set(),
        // incrementing i, etc.
        state = &quot;forLoop&quot;;
        colors.set(j+1, key);
        i++;
        return;
    }
    // update colors, value of j, etc...
}


// Executes everything before the while loop.
void doForLoopBody() {
    if (isThisIterationOfForDone()) {
        state = &quot;END&quot;;
        return;
    }

    // update colors, get values of key and j initialized, etc

    // switch to processing the body of the while loop
    state = &quot;whileLoop&quot;;
}

huangapple
  • 本文由 发表于 2020年8月18日 02:22:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/63456563.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定