【问题标题】:How to sort vector using insertion sort?如何使用插入排序对向量进行排序?
【发布时间】:2021-12-07 13:41:17
【问题描述】:

我只是在练习使用向量实现选择排序。但它返回的向量与我输入的向量相同。

此实现适用于数组但不适用于向量。

#include<bits/stdc++.h>
using namespace std;
vector<int>  insertionSort(vector<int> v, int n)
{
    for(int i=1;i<=n-1;i++)
    {
        int currentEle = v[i];
        int prevEle = i-1;
        // right index where current is inserted
        while(prevEle>=0 and v[prevEle]>currentEle){
            v[prevEle+ 1] = v[prevEle];
            prevEle = prevEle-1;

        }
       v[prevEle+1] = currentEle;
    }
      return v;
    
}
int main()
{
    int n;
    cin>>n;
    vector<int> v(n);
    for(int i=0;i<n;i++)
    {
        cin>>v[i];
    }
    insertionSort(v,n);
    for(auto x: v)
    {
        cout<<x<<" ";
        
    }

  //OUTPUTS
    
    Input : 5 3 1 2 4
    Expected Output : 1 2 3 4 5
    My Output : 5 3 1 2 4 

有没有使用相同算法的选择排序对向量进行排序的特定方法?

【问题讨论】:

  • “按值传递”,在任何 C++ 教程中都有解释,与排序或向量无关。进一步:这不是minimal reproducible example。此外,向量具有查询其大小的方法,无需将其作为参数传递。作为这里的新用户,也请带上tour并阅读How to Ask
  • Edit 包含minimal reproducible example 的问题。显示调用insertionSort的代码并显示结果。
  • 首先,currentEle 看起来是指当前元素。但是,prevEle 是指上一个索引。这非常令人困惑。请使用适当的变量名并减少代码混淆。谢谢!
  • 请同时显示调用代码。
  • “但它返回的向量与我输入的向量相同。” -- 你的代码没有证明这一点。但是,鉴于我们没有您的功能的功能规格,散文中可能存在一些歧义。您是否打算 insertionSort() 通过 return 语句返回一个新向量或更改作为参数提供的向量(或两者)? “返回”结果的两种方式都是有效的,但每种方式都需要对您的代码进行不同的修复。

标签: c++ vector c++17


【解决方案1】:

您没有将返回的向量从insertionSort 存储到任何东西中。

你可以这样做:

std::vector<int> result = insertionSort(v,n);

代替:

insertionSort(v,n);

然后打印result 而不是v

for (auto &x : result) {
  std::cout << x << " ";
}

与问题无关但有帮助的其他几点。

  1. Using using namespace std is considered a bad practice
  2. Why you should not use #include&lt;bits/stdc++.h&gt;

【讨论】:

    【解决方案2】:

    我的问题的解决方案是使用向量作为值或参考

    vector<int>  insertionSort(vector<int> &v, int n)
    

    在按引用传递中,实际参数传递给函数。在按值传递的情况下,参数值复制到另一个变量。所以最好的方法是使用向量作为参考

    【讨论】:

    • 这看起来不错,但很危险。您想通过值获取向量并返回结果,或者通过非常量引用获取它并将其改变到位。由于std::sort 在容器上运行,我建议使用vector&lt;int&gt;&amp; 并返回void。另外,既然可以使用v.size(),为什么还要使用n?我认为,使用std::rotate 进行插入也会很好地为您服务。
    【解决方案3】:

    您正在将向量按值传递给 insertionSort 函数,但在 ma​​in 函数中,您没有收集返回值。您最终打印了相同的输入向量 v,因此,您得到了相同的输出。

    所以你的main函数应该是这样的。

    int main()
    {
        int n;
        cin>>n;
        vector<int> v(n);
        for(int i=0;i<n;i++)
        {
            cin>>v[i];
        }
        auto sortedVector = insertionSort(v,n);
        for(auto x: sortedVector)
        {
            cout<<x<<" ";
            
        }
        return 0;
    }
    

    【讨论】:

      【解决方案4】:

      让我们像调试器一样单步调试您的代码。在这里,我们将在即将调用insertionSort(v,n); 的行处放置一个断点。我们假设用户已经输入了有效的未排序输入整数。我会随意使用向量{3,2,9,5,7},这在你的情况下并不重要。

      我们将像您的调试器一样进入代码,现在这会因您的特定编译器和调试器工具而异,但为简单起见,我们可以根据 C++ 语言的规则概括此处发生的情况。

      这里的问题不会在运行时发生。这里的问题发生在编译时。它仍然是有效的代码,因为没有生成编译错误,所以您认为它一定是运行时错误,因为您没有得到正确的输出。

      在这里,您必须了解 C++ 编译器如何解释函数声明/定义和函数组合。编译时,它会查找您声明为 insertionSort() 的函数,该函数必须在首次使用之前定义。在你的情况下是。您在代码的第 3 行声明并定义了它。接下来它必须确定它的返回类型和参数类型是否匹配。

      您在第 30 行的呼叫站点:insertionSort(v,n);

      在第 3 行找到匹配的声明/定义:vector&lt;int&gt; insertionSort(vector&lt;int&gt; v, int n); 它返回一个 vector&lt;int&gt;,但在 C++ 中函数调用点的返回类型可以忽略。接下来是参数类型,因为它采用vector&lt;int&gt;int 类型的value

      当您的源代码被编译成目标代码,然后链接并翻译成程序集时。这里发生的情况是,如果您不使用指针或动态内存(堆),则此函数将拥有自己的堆栈帧,其中这些变量仅可见并且在此函数的范围内具有生命周期。

      当此函数返回调用站点时,这些变量将被销毁。此外,按值传递通常会导致传入的值的副本。

      下面是这个函数的堆栈帧的样子: 在这里,我将在文字算法图中使用伪汇编助记符。现在,由于编译器优化和具有多个缓存的现代硬件以及向量内在函数,这是现代硬件的简化,因为它们超出了这里发生的范围,所以可以忽略。

      • rsp -> 寄存器堆栈指针
      • ret -> 从过程中返回
      • rpt -> 栈帧的过程返回指针
      • r0 -> 零寄存器
      • [r1 - rn] -> 任意多用途寄存器
      • mov -> 操作码或指令,通过立即数或其他寄存器或内存位置将值移动、存储或加载到某个寄存器中。
       //Stack Frame:
       {   
           // Setup the stack and frame pointers... internal housekeeping
      
           // Return Type Setup: vector<int>
           rpt assigned to the first value in `vector<int>`
           // this is reserved memory specifically for return values 
      
           // Parameter Type Setup: vector<int>, int
      
           // n's initialized value stored in reg1
           mov r1, n's value
      
           // values to be stored {3,2,9,5,7}
           mov r2, 3
           mov r3, 2
           mov r4, 9
           mov r5, 5
           mov r6, 7
      
           /* Function Implementation. Not going to convert this to assembly
              However, it will use the rpt pointer to construct the new
              vector that is local to this function after the necessary 
              checks are performed... 
      
              for(int i=1;i<=n-1;i++)
              {
                  int currentEle = v[i];
                  int prevEle = i-1;
                  // right index where current is inserted
                  while(prevEle>=0 and v[prevEle]>currentEle){
                      v[prevEle+ 1] = v[prevEle];
                      prevEle = prevEle-1;
                  }
                  v[prevEle+1] = currentEle;
              }
            */
      
           // Make sure that shared registers are reassigned or their original values
           // are restored before returning from procedure.
           // More internal housekeeping...
      
           // Assuming the algorithm is correct and the local registers were assigned
           // to the designated memory pointed to by rpt
           // Then prt would look something like:
           // {rpt[0] = r3, rpt[1] = r2, rpt[2] = r5, rpt[3] = r6, rpt[4] = r1}
           ret rpt
       } 
      

      这是您的堆栈框架在汇编中的样子的伪示例。现在,ret 就像 C/C++ 中的指针,但在汇编中它是一个特殊寄存器,用于保存 CPU 寄存器文件中另一个寄存器或寄存器段的内存地址,甚至可能指向现代系统中的缓存......

      现在,在您的 C++ 代码中,我们已经到达了这个函数的末尾,它超出了范围。所有局部变量都被销毁,因为该内存被归还给系统和/或操作系统......

      在调用这个函数的过程中,你永远不会assign它从它的返回值到任何变量。您通过值将v 传递给此函数,并将副本复制到堆栈中。您在此函数内部创建了一个临时向量并执行了排序算法。你从这个函数返回并且从不使用它的返回值。

      在 C++ 代码的下一行部分中,您正在使用基于范围的 for 循环并遍历 main 的变量 v,该变量从用户输入中分配了值 {3,2,9,5,7}

      您通过值将其传递到您的函数中,该值将信息复制到临时文件中。并且 main 的 v 变量位于其自己的堆栈框架中,永远不会被修改。 insertionSort()' 堆栈帧中的临时返回变量是被更改的那个。

      这不是编译时错误,也不是运行时错误。由于没有完全理解语言的规范以及编译器如何处理它,这只是算法的糟糕实现设计。

      要修复此函数,您可以删除返回类型并通过引用传递。签名将如下所示:

      void insertionSort(vector<int>& v, int n);
      

      这里将变量的引用传递给函数。这一次,当您的编译器看到这一点并稍后生成目标代码以翻译成程序集时。它不会复制这些值,而是直接使用相同的寄存器或缓存行。

      在这种情况下,传递给此函数的对象可以在对其执行任何计算后进行修改。

      这是一个基本整数加法函数的简单示例,它完全复制了您所做的:

      // Pass By Value:
      int add(int a, int b) {
          a = a+b;
          return a;
      }
      
      int main() {
          int a = 3;
          int b = 5;
          add(a, b);
          std::cout << a;
          return 0;
      } 
      

      在这里您希望看到 8 被打印出来,但实际上来自 main 的 a 从未被修改过,3 将被打印到您的控制台并且您从未使用过该函数的返回值。

      让我们检查一下通过引用的版本:

      void add(int& a, int b) {
          a = a+b;
          return a;
      }
      
      int main() {
          int a = 3;
          int b = 5;
          add(a, b);
          std::cout << a;
          return 0;
      }
      

      在这个版本中,因为在 add() 的堆栈帧中直接使用了对 main 的 a 的引用,所以 main 的 a 将被修改。这一次,当您打印到控制台时,您将看到一个8

      我希望这有助于理清在 C++ 中将参数类型传递给函数的不同方式。您可以按值、按引用和按指针传递,其中一些具有 const 版本,这超出了本主题的范围。此外,在实现算法时,不要依赖您的 parameter 或函数 argument 排序。在生成必要的程序集时,特定编译器将如何设置函数的参数或参数列表没有规范或单一使用约定或保证。有各种调用约定,您必须知道您正在使用哪个编译器以及正在使用什么调用约定。

      我希望这有助于您了解您的代码发生了什么,不同抽象级别的底层发生了什么以及为什么您会得到您得到的结果,并帮助您了解这是什么类型的错误.

      其他人似乎只是说,“传递价值而不是引用”,假设您知道它们是什么和意思……我对待您就像这是您第一次查看代码或新语言一样。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-08-26
        • 2016-05-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多