【问题标题】:Merge Sort, I/O using files shows Segmentation Fault合并排序,使用文件的 I/O 显示分段错误
【发布时间】:2014-02-01 20:00:05
【问题描述】:

以下用 C++ 编写的合并排序程序在通过控制台提供输入时可以正常工作。但是当我使用文本文件提供输入时,它会给我一个分段错误。我尝试在代码的各个部分打印消息以检查错误在哪里,但除了 d seg-fault 消息之外,它没有打印任何内容。 这是代码。当我不使用文件提供输入/显示输出时,它工作正常。

我在一个基本内存为 1 GB 的 virtualbox 4.0.8(ubuntu OS) 中的 gcc 编译器上运行了这个 prog。 cud错误是由于内存不足引起的?

#include<iostream>
#include<fstream>
#include<sys/time.h>
using namespace std;

int array[50];
int barray[50];

void merge(int p,int q, int r)
 {
    int i,j,k,l;
    l=p;
    i=p;
    j=q+1;

    while((i<=q)&&(j<=r))
    {

     if(array[i]<=array[j])
        barray[++k]=array[i++];

     else
        barray[k++]=array[j++];

    }

    if(i>q)
    {  
       for(l=j;l<=r;l++)
       { 
         barray[k]=array[l];
         k++;
       }
    }

   else
   {
      for(l=i;l<=q;l++)
      {
        barray[k]=array[l];
        k++;
      }

   }

   for(i=p;i<=r;i++)
        array[i]=barray[i];
}

void mergesort(int p, int r)
{
   int q;

   if(p<r)
      q=(p+r)/2;

   mergesort(p,q);
   mergesort(q+1,r);
   merge(p,q,r);

}


int main()
{
  int r, p, i;

  struct timeval tv1, tv2;/* For finding the running time */
  gettimeofday(&tv1, NULL);

  ifstream fin;
  ofstream fout;

  fin.open("abc5.txt");
  fout.open("new5.txt");

  fin>>r;
  while(fin)
  {

    for(i=1;i<=r;++i)
    {
       fin>>array[i];
    }

  }

  mergesort(1,r);

  for(i=1;i<=r;++i)
  {
    fout<<array[i]<<"\n";
  }

  gettimeofday(&tv2, NULL);
  fout<<"Running Time: "<<(double) (tv2.tv_usec - tv1.tv_usec) / 1000000 + (double)    (tv2.tv_sec - tv1.tv_sec)<<" sec";


 fin.close();
 fout.close();

 return 0;

}

输入文件 8 3 6 2 8 9 1 4 10

【问题讨论】:

  • 也可以发布输入文件,因为它最好不要超过 50 行。如果是这样,再加上 C/C++ 数组基于 1 和 0 的不正确使用,很容易调用 UB。
  • 注意:在将k 用作合并算法中取消引用的索引之前,您永远不会初始化它。仅此一项就会调用未定义的行为。另外,检查两个主要任务中k 的增量。它并不一致,它需要是。老实说,合并算法只需要彻底改造。
  • 对不起,得吃午饭了。此外,如果(p&lt;r) 曾经是false,则q 可以在您的mergsesort 算法中未初始化;还有更多未定义的行为。正如我所说,这需要返工并更好地理解未定义的行为和基于 0 的数组。将同一拆分数组中的两个段合并到临时存储中,然后复制回原始存储的算法在某种程度上对于合并排序算法至关重要。所说的合并算法需要solid

标签: c++ segmentation-fault mergesort


【解决方案1】:

您的索引还有很多不足之处。我不会害羞地告诉你,也不会简单地说我不会以这种方式进行合并排序。但它就是这样,所以我会介绍它。

重要的是要理解,使用分治算法的序列排序都是关于在序列中传递一些碱基引用以及与该引用的一些偏移量。该算法应该独立于外部变量(如array[]barray[]),而是将它们作为参数。此外,请记住您正在使用 C++(或 C)进行编程。 两种语言的部分优点在于它们原生的指针算术能力。它可以很好地用于合并排序等算法。在我演示了你的合并和合并排序函数应该如何工作之后,我将演示我在说什么,然后我会提供你可以在 C++ 中使用的最简单的合并排序标准库功能。


您的代码优先

首先使用您的参数重新滚动您的函数。我冒昧地将参数重命名为它们实际上对审阅者有意义。该算法应该是不言自明的,我敦促您将其与您正在做的事情并排比较。

//  low  = starting index
//  mid  = middle index
//  high = last index
void merge(int low, int mid, int high)
{
    int i=low, j=mid, k=0;

    while (i < mid && j <=high)
    {
        if (array[i] < array[j])
            barray[k++] = array[i++];
        else
            barray[k++] = array[j++];
    }

    // finish whichever segment isn't done
    while (i < mid)
        barray[k++] = array[i++];
    while (j <= high)
        barray[k++] = array[j++];

    // copy back
    for (i=0;i<k;++i)
        array[low+i] = barray[i];
}

void mergesort(int low, int high)
{
    int len = high - low;
    if (len < 2)
        return;

    int mid = low + len/2;
    mergesort(low, mid);
    mergesort(mid, high);
    merge(low, mid, high);
}

这个算法需要注意的是,传递的初始参数必须是序列中的有效索引。换句话说,如果您读取一个包含 8 个元素的数组,有效索引是0..7,因此您可以将其调用为mergesort(0,7)


另一种方法

C/C++ 中的传统合并排序使用数组基索引和长度作为其唯一参数。中点由排序算法计算。尽管合并算法并不严格需要它(它也可以只使用长度/2),但它可以在您需要从现在中间未拆分的段合并的情况下提供更强大的算法。

不要纠结于下面 std::vector、std::random_device 等的使用。它们在main() 函数中仅用于填充随机数数组,然后对其进行排序,并在排序操作之前和之后显示。我想让你摆脱的是实际的算法本身。

#include <iostream>
#include <vector>
#include <random>

void merge(int ar[], std::size_t mid, std::size_t len)
{
    if (len < 2)
        return;

    // temporary storage. normally I use a RAII container
    //  such as std::vector<>, but I wanted to demonstrate
    //  the algorithm and indexing, not the memory management.
    //  none the less it is worth noting.
    int *tmp = new int[len];
    std::size_t i=0, j=mid, k=0;

    while (i < mid && j < len)
    {
        if (ar[i] < ar[j])
            tmp[k++] = ar[i++];
        else
            tmp[k++] = ar[j++];
    }

    // complete the unfinished segment
    while (i < mid)
        tmp[k++] = ar[i++];
    while (j < len)
        tmp[k++] = ar[j++];

    // and move back to the original array
    for (i=0; i<len; ++i)
        ar[i] = tmp[i];

    delete [] tmp;
}

void mergesort(int ar[], std::size_t len)
{
    if (len < 2)
        return;

    // note pointer arithemtic in second call.
    mergesort(ar, len/2);
    mergesort(ar+len/2, len - len/2);
    merge(ar, len/2, len);
}

int main()
{
    std::vector<int> data;
    data.reserve(20);

    std::random_device rd;
    std::default_random_engine rng(rd());
    std::uniform_int_distribution<> dist(1,50);

    // populate array
    std::generate_n(std::back_inserter(data),
                data.capacity(),
                [&](){ return dist(rng);});

    // show on-screen
    for (auto n : data)
        std::cout << n << ' ';
    std::cout << '\n';

    // mergesort
    mergesort(data.data(), data.size());

    // show on-screen
    for (auto n : data)
        std::cout << n << ' ';
    std::cout << '\n';

    return 0;
}

输出(可变)

15 10 8 38 20 21 9 43 8 22 19 45 12 16 17 36 2 32 6 37 
2 6 8 8 9 10 12 15 16 17 19 20 21 22 32 36 37 38 43 45 

你会讨厌的部分

在完成所有这些工作之后,您会不太高兴知道标准库中已经存在两种为您进行段合并的算法,即std::mergestd::inplace_merge。使用其中之一可以实现mergesort 琐碎,如下所示:

#include <algorithm>

void mergesort(int ar[], std::size_t len)
{
    if (len < 2)
        return;

    mergesort(ar, len/2);
    mergesort(ar+len/2, len-len/2);
    std::inplace_merge(ar, ar+len/2, ar+len);
}

请记住,如果您需要使用除 std::sort 之外的其他东西,老实说,除了实现学术目的之外,这一切都几乎无用。

【讨论】:

  • 这可能与课堂作业有关,而不是他推出自己的实现。
  • @AlbertMyers 我同意,在这种情况下,我不会像许多其他人经常做的那样跳上“只使用 std::sort”的潮流。这就是三部分答案的重点:(a)这是您的代码出了什么问题,(b)这是一种不同的方式,以及(c)这是最少工作的方式。希望有一天“我不必这样做,因为它已经在库中”的舒适感会开始。
【解决方案2】:

因此,根据您的输入,看起来当 p=1 和 r=1 时,您在随后的合并排序调用中使用未初始化的 q 值。

您收到的错误是由于合并排序函数中的无限递归。

【讨论】:

  • 我只尝试了 8 个元素,它也不起作用。
  • @WhozCraig 说了什么......你能发表你的意见吗?
  • 感谢您指出这一点!但即使在初始化 q 和 k 并将索引更改为基于 0 的数组之后,它仍然显示 seg-fault!
  • 我认为@WhozCraig 已经给出了你的答案。你应该考虑重写你的算法。也就是说,如果您想找出 this 实现的行为方式的原因,您可能需要启动 GDB 并在它运行时单步执行它。学习使用 gdb 将为您未来的职业生涯带来红利。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-10-18
  • 1970-01-01
  • 2021-08-11
  • 2015-01-21
  • 2021-12-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多