算法可视化:实现没有循环但每次调用函数时增量可变的插入排序

Posted

技术标签:

【中文标题】算法可视化:实现没有循环但每次调用函数时增量可变的插入排序【英文标题】:algorithm visualization: implementing insertion sort without loops but with variable increments everytime a function is called 【发布时间】:2020-08-17 18:22:00 【问题描述】:

我正在处理中构建排序算法可视化器(使用额外的可视化库扩展 java),我非常坚持这个问题,我认为其他人将能够帮助我解决这个问题。 在处理过程中,有一个名为 draw() 的函数每秒被调用 60 次。我想在这里执行,每次调用 draw() 时,插入算法的一个步骤。我已经用冒泡排序实现了它。 (见下面的代码)。 updateBubble() 在 draw() 中被调用,'colors' 是我用来保持不同颜色值排序的数组列表的名称。

图片以获得更好的理解: [![可视化算法预览][1]][1]

...
int j = 0
...
void updateBubble() 
  bubble.sort(j);
  j++;
  if (i<bubble.colors.size()) 
    if (j >= bubble.colors.size()-i-1) 
      j = 0;
      i++;
    
   else 
    bubble.sorted = true;
  

这里是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();

void sort() 
    int n = colors.size(); 
    for (int i = 1; i < n; ++i)  
      int key = colors.get(i); 
      int j = i - 1; 
      while (j >= 0 && colors.get(j) > key)  
        colors.set(j+1, colors.get(j));
        j = j - 1;
       
      colors.set(j+1, key);
    
  

这就是我现在得到的:这仍然是错误的,但越来越接近并清楚我想要达到的目标,创建一个仅适用于增量和 if 语句而不是 while 和 fors 的函数,因此每个不同的步骤都是每次调用该方法时执行。

  // i resembles for loop variable
  if (i<insertion.colors.size()) 
    if (j<0 || insertion.colors.get(j) <= 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;
    
  
                                                                                                                                       

编辑:我修复了它,您可以在下面找到我的解决方案 :) 每次调用 updateInsertion() 时,我的代码都会在算法中执行准确的一步!感谢所有努力评论的人,我不知道这是否是最佳实践,所以如果你愿意,请随时更新!

void updateInsertion() 

  // i resembles for loop variable

  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;
    
  

【问题讨论】:

我会像 ***.com/questions/11570132/… 那样使用迭代器。现在您可以逐步执行算法,并控制绘图代码。 嘿,感谢您的快速评论!我并没有真正关注这将如何帮助我或我将如何实施?有一个循环和一个while循环,如何每次在这个过程中用迭代器调用draw()时只做一个步骤? 【参考方案1】:

我喜欢这个项目。

Processing 还有一个millis() 方法,它返回自从你开始你的草图以来花费了多少毫秒。我有时用它来计时我的动画,这在这里可以派上用场。下面是一个计时器类的实现:

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

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

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
  

您可以微调数据的排序速度,并且仅在数据发生变化时对其进行绘制。这是双赢的!

玩得开心!


编辑

为了帮助您实现,这里有一个示例。您可以将此代码复制并粘贴到一个空的处理草图中,它会按原样运行。为了让我的工作更轻松,我打印到控制台而不是使用图形显示,但你应该能够得到我正在做的事情。

这里的秘密是我的排序算法已经过微妙的修改,所以当我调用它们时,它们总是只运行一个排序步骤。自己看:

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<_numberOfItems; i++) 
    _bubbleList.append((int)random(10, 99));
  
  for (int i=0; i<_numberOfItems; i++) 
    _selectionList.append((int)random(10, 99));
  
  for (int i=0; i<_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 < _numberOfItems) 
      singleStepSelectionSort(_selectionList, _sortingStep);
      singleStepInsertionSort(_insertionList, _sortingStep);
    
    _sortingStep++;

    // update the display (I'm printing to console instead for simplicity)
    for (int i : _bubbleList) 
      print(i + " ");
    
    print("  |  ");
    for (int i : _selectionList) 
      print(i + " ");
    
    print("  |  ");
    for (int i : _insertionList) 
      print(i + " ");
    
    print("\n");
  


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


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

  for (int i=0; i<list.size()-1; i++)  
   
    if (list.get(i) > 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 "single-step" implementation of Selection Sort
void singleStepSelectionSort(IntList list, int step) 
 
  int min_idx = step; 
  for (int j = step+1; j < list.size(); j++) 
    if (list.get(j) < 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() > limit);
  

如果您有任何问题,请告诉我。


更多编辑:

插入排序的每次交换都意味着很多很多交换。这真的很痛苦,因为这种算法很难停下来。

幸运的是,我不在乎。跳出框框思考,我选择创建一个专用于对数组进行排序的类,同时记录如何对其进行排序,然后能够“就像实时发生一样”回放它。看看:

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<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("Step " + String.format("%02d", sortingStep) + ": ");
  printArray(managedSelectionSort.list);
  print("\n");


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

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

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


// this class is where the magic happens
// we'll sort the array all at once while recording every move
// then we'll play back those moves on a copy of the array
class ManagedSelectionSort 
  IntList list, hiddenList;  // list is the "official" list, while hiddenList is where the heavy lifting happens
  ArrayList<SwapIndex> 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<SwapIndex>();

    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'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's just a matter of playing them back on a copy of the initial array
  void sortOneStep() 
    if (sortingStep < 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<hiddenList.size(); i++) 
      int j = i;

      while (j>0 && hiddenList.get(j) < 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'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 'record', 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 + " ");
  


class Delay 
  int limit;

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

  boolean expired ()     
    return millis() > limit;
  

如果我这次理解正确的话,这应该可以解决您最初的问题!

【讨论】:

嘿!感谢您的回答,我肯定会实现一个算法计时器:)问题是,我希望 4 个算法同时以相同的速度执行步骤.. 所以他们的步骤方法都应该被调用画。如果我在抽签中延迟,我仍然无法逐步执行排序算法:) 有道理。信不信由你,但我昨晚想到了:你可以把延迟放在排序算法中。这样,每个算法都可以在您选择的时间步调。确保每个延迟对象都是不同的,否则您可能会意外放弃一些排序步骤。如果这种实现方式让你感到奇怪,请告诉我,我会向你展示一段代码 sn-p,它应该会让它更明显。 如果我在算法中放了一个延迟,是不是除非前一个算法结束,否则下一个算法不会被调用?还是我错了? 我可以假设您希望所有排序算法以相同的速率逐步推进吗? @ArnePannemans 我用一些代码更新了答案,这应该可以帮助你理解我的意思。【参考方案2】:

实现此目的的一种方法是通过某种存储状态。下面是我所说的高层次内容。

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

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


// Single iteration of a loop.
void draw()
    switch(state) 
        case "forLoop":
            doForBody();
            break;
        case "whileLoop":
            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'm doing on the next couple lines here would
        // be to introduce an additional state (ex: "postWhile") that would
        // execute just after this method and would handle the colors.set(),
        // incrementing i, etc.
        state = "forLoop";
        colors.set(j+1, key);
        i++;
        return;
    
    // update colors, value of j, etc...



// Executes everything before the while loop.
void doForLoopBody() 
    if (isThisIterationOfForDone()) 
        state = "END";
        return;
    

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

    // switch to processing the body of the while loop
    state = "whileLoop";

【讨论】:

以上是关于算法可视化:实现没有循环但每次调用函数时增量可变的插入排序的主要内容,如果未能解决你的问题,请参考以下文章

序列的增量赋值 +=和*=

kettle 6.1 按时间循环增量抽取数据

如何为 React Native 中调用的每次 onPress 函数提供自动增量?

可视化希尔排序算法

函数的递归调用与二分法

matlab 希尔算法可视化