【问题标题】:Stack versus Integer堆栈与整数
【发布时间】:2011-09-29 05:25:34
【问题描述】:

我创建了一个程序来解决数据结构类的密码算法问题。教授建议我们使用由链接节点组成的堆栈来跟踪我们用哪些数字替换了哪些字母,但我意识到整数也可以起到同样的作用。代替堆栈 {A, 1, B, 2, C, 3, D, 4} 我可以在 1234 中保存相同的信息。

不过,我的程序运行起来似乎比他给我们的估计要慢得多。有人可以解释为什么堆栈的行为会更有效吗?我已经假设了,因为我不会一遍又一遍地调用方法(push、pop、top 等),而只是在我的更快的“解决方案”中添加一个。

这不是一个开放式问题,所以不要关闭它。虽然你可以用不同的方式实现,但我想知道为什么在 C++ 的核心,通过堆栈访问数据比存储在 int 中和通过 mod 提取具有性能优势。

虽然这是作业,但我实际上并不需要帮助,只是非常好奇和好奇。

谢谢,迫不及待想学习新东西!

编辑(添加一些代码)

letterAssignments 是一个大小为 26 的 int 数组。对于像 SEND + MORE = MONEY 这样的问题,不使用 A,因此 letterAssignments[0] 设置为 11。所有使用的字符都初始化为 10。 answerNum 是一个数字,其位数与唯一字符数一样多(在本例中为 8 位)。

int Cryptarithmetic::solve(){
while(!solved()){       
    for(size_t z = 0; z < 26; z++){
        if(letterAssignments[z] != 11) letterAssignments[z] = 10;
    }
    if(answerNum < 1) return NULL;
    size_t curAns = answerNum;

    for(int i = 0; i < numDigits; i++){ 
        if(nextUnassigned() != '$') {
            size_t nextAssign = curAns % 10;
            if(isAssigned(nextAssign)){
                    answerNum--;
                    continue;
                }
            assign(nextUnassigned(), nextAssign);
            curAns /= 10;
        }
    }
    answerNum--;
}
return answerNum;
}

两个帮助方法,如果您想查看它们:

char Cryptarithmetic::nextUnassigned(){ 
char nextUnassigned = '$';
for(int i = 0; i < 26; i++) {
    if(letterAssignments[i] == 10) return ('A' + i);
}
}

void Cryptarithmetic::assign(char letter, size_t val){
assert('A' <= letter && letter <= 'Z');  // valid letter
assert(letterAssignments[letter-'A'] != 11); // has this letter
assert(!isAssigned(val)); // not already assigned.
letterAssignments[letter-'A'] = val;
}

【问题讨论】:

  • 您如何存储和访问这些整数?
  • 我从 int answerNum = 0 开始,然后将其设置为 123456...(其数字 == 问题中唯一字符的数量)。它是类中的一个全局变量,所以如果这就是你的意思,则没有 getter/setter。
  • 您打算如何将 26 个单独的数字存储在一个整数中?你能附上一些代码吗?
  • 性能取决于许多因素,但主要取决于算法复杂性和 I/O 量(内存访问也是 I/O)。如果没有看到您的代码,就很难说出效率低下的地方和地方。另外,我不认为简单地存储数字是正确的。您很可能应该将字母替换为无法简单追溯到原始字母的其他字母或数字,即 A-10,B-1,C-23,D-4,E-15,.. .
  • @Serdalis 我为你添加了一些代码 :)

标签: c++ integer stack cryptarithmetic-puzzle


【解决方案1】:

从外观上看,你在这里做事的方式非常低效。

作为一般规则,尽量减少 for 循环的数量,因为每个循环都会大大减慢您的实现速度。

例如,如果我们去掉所有其他代码,您的程序看起来像

while(thing) {
  for(z < 26) {

  }
  for(i < numDigits) {
    for(i < 26) {

    }
    for(i < 26) {

    }
  }
}

这意味着对于每个 while 循环,您正在执行 ((26+26)*numDigits)+26 个循环操作。那是假设isAssigned() 不使用循环。

理想情况下:

while(thing) {
  for(i < numDigits) {
  }
}

我确信通过更改您的代码是可能的。 这就是为什么使用整数数组的实现比使用不使用for(i &lt; 26) 循环的堆栈的实现慢得多(我假设)。

然而,在回答您最初的问题时,存储整数数组总是比您想出的任何结构都快,因为分配内存、调用函数等涉及更多开销。

但与所有事情一样,实现是慢速程序和快速程序之间的关键区别。

【讨论】:

  • 这两种方法都有一个大小为 26 的 int 数组来表示每个字母应该分配的内容,不同之处在于我们现在正在尝试什么组合。我的想法与开销相同,这就是我感到困惑的原因。教授在课堂上跑了他的课,它在大约 6 秒内解决了这个问题。我的需要更长的时间。
  • 将此信息存储在连续的内存空间(int、数组等)中应该比链表快得多。但是,如果您不以快速方式访问值,则链表会更快。
  • 那么您是否认为整数数组会更快,因为整数需要所有这些模块和除法?
  • 绝对是。如果有的话,分配数组 > 分配 int 的开销可以忽略不计。
【解决方案2】:

问题在于,通过计数,您也在考虑重复,问题何时可能要求为每个不同的字母分配一个不同的数字,以便数字方程成立。

例如,对于四个字母,您正在测试 10*10*10*10=10000 字母->数字映射而不是其中的 10*9*8*7=5040(字母的数量越大,两个数字之间的比率越大......)。

【讨论】:

  • 当我实现它时,当它修改数字时,它会检查数字中的前一个数字是否已分配,如果是,则递增然后继续。因此,即使是前两位数字,它也会跳过 10% 的数字(00、11、22 等)。它不会测试像 12345671 这样的数字
【解决方案3】:

mod 函数使用的 div 指令非常昂贵。将它用于您的目的很容易比良好的堆栈实现效率低。这是一个指令时序表:http://gmplib.org/~tege/x86-timing.pdf

您还应该为基于 int 的堆栈编写单元测试,以确保它按预期工作。

【讨论】:

    【解决方案4】:

    编程实际上是用内存换时间,反之亦然。 在这里,您将数据打包成整数。您节省了内存,但浪费了时间。

    速度当然取决于堆栈的实现。 C++ 是带有类的 C。如果你不使用类,它基本上是 C(和 C 一样快)。

    const int stack_size = 26;
    
    struct Stack
    {
      int _data[stack_size];
      int _stack_p;
      Stack()
      :_stack_size(0)
      {}
      inline void push(int val)
      {
         assert(_stack_p < stack_size); // this won't be overhead 
                                        // unless you compile debug version(-DNDEBUG) 
         _data[_stack_p] = val;
      }
    
      inline int pop()
      {
        assert(_stack_p > 0);       // same thing. assert is very useful for tracing bugs
        return _data[--_stack_p];  // good hint for RVO
      }
    
      inline int size()
      {
        return _stack_p;
      }
    
      inline int val(int i)
      {
        assert(i > 0 && i < _stack_p);      
        return _data[i];
      }
    }
    

    没有像 vtbp 这样的开销。 pop() 和 push() 也非常简单,因此它们将被内联,因此没有函数调用的开销。使用 int 作为堆栈元素也有利于速度,因为 int 保证是最适合处理器的大小(不需要对齐等)。

    【讨论】:

    • 有趣。但是,我们将使用的堆栈包含 Stack.cc 和 Stack.h,因此实现它需要创建 Stack 类的实例。我可以看到一个结构非常有效,因为它在类中,但是进行数千次方法调用似乎很昂贵。
    • 方法是内联的(inline 指令),因此编译器将放置函数体而不是函数调用。所以 a.size()a._stack_p 之间的差异实际上是零
    • 很多人会不同意你关于“C++ 是带类的 C”的观点。
    猜你喜欢
    • 1970-01-01
    • 2014-06-23
    • 2011-05-04
    • 1970-01-01
    • 2011-09-06
    • 2014-05-31
    • 2012-01-18
    • 2021-02-18
    • 1970-01-01
    相关资源
    最近更新 更多