【问题标题】:pointer to pointer in python指向python中的指针的指针
【发布时间】:2015-09-25 08:17:26
【问题描述】:

我对 python 比较陌生,正在尝试了解如何将 Python 与 C/C++ 联系起来。考虑这个问题:检查给定的链表是否是回文。从这个source,我找到了一个非常有效的解决方案:

// Initial parameters to this function are &head and head
bool isPalindromeUtil(struct node **left, struct  node *right)
{
   /* stop recursion when right becomes NULL */
   if (right == NULL)
      return true;

   /* If sub-list is not palindrome then no need to
       check for current left and right, return false */
   bool isp = isPalindromeUtil(left, right->next);
   if (isp == false)
      return false;

   /* Check values at current left and right */
   bool isp1 = (right->data == (*left)->data);

   /* Move left to next node */
   *left = (*left)->next;

   return isp1;
}

// A wrapper over isPalindromeUtil()
bool isPalindrome(struct node *head)
{
   isPalindromeUtil(&head, head);
}

基本上一个指向指针的指针被分配到前面(左指针),一个指针被分配到列表的末尾(右指针)。一旦指针到达它们各自的位置,它们就会递归并找到匹配的值。这里,*left 维护指针的状态,该指针的值通过递归循环持续存在。 python 中的一种解决方案是将左指针与布尔值一起传回。但是,如果使用多个指向指针的指针,则返回元素的数量会爆炸!糟糕的设计。还有其他方法吗?

编辑:

非常感谢您的回复!我忘了提到我不是想在这里解决回文问题。我试图了解是否存在使用指针的 Pythonic 等效方式。我想要理解的是,如果向我提出一个问题,它使用链表等数据结构,我应该尝试将其转换为其他数据结构,如列表,还是我需要创造性并使用解决问题相同的数据结构。由于指针指针在链表和 BST 中是非常重要的概念,对于这个概念是否有等效的解决方案或变通方法?

编辑#2: 我想出的粗略代码如下。但是,left_ptr 仍然停留在相同的位置,因为它是一个指针而不是指向指针的指针:

def palindrome_utils(self, left_ptr, right_ptr):
        if right_ptr is None:
                return True
        prev_result = self.palindrome_utils(left_ptr, right_ptr.get_link())

        cur_result = left_ptr.get_data() == right_ptr.get_data()
        left_ptr = left_ptr.get_link()
        return cur_result and prev_result

def palindrome(self):
        return self.palindrome_utils(self.head, self.head)

【问题讨论】:

  • 另一种使用指针的方法,或者另一种确定单词是否为回文的方法?
  • 当你说“链表”时,你是在使用collections.deque吗?
  • 尝试在 Python 中复制 C/C++ 数据结构和算法并不总是有用的。请记住,由于解释器的开销,当用 Python 而不是 C/C++ 实现时,各种算法的(相对)速度可能会有很大的不同。例如,与在 Python forwhile 循环中对列表项进行操作相比,对序列进行操作的 Python 库函数可以非常快,因为该函数可以以 C 速度处理列表。 TL; DR:当你用 Python 编写代码时,不要尝试编写 C/C++。

标签: python c pointers python-3.x


【解决方案1】:

编辑:将l == l[::-1] 添加为is_palindrome5,这非常快,并且是迄今为止最具可读性和pythonic 的。

我能检查回文最快的是这个单行:

def is_palindrome1(l):
    return l[:len(l) // 2] == l[(len(l)+1) // 2:][::-1]

在您的问题中使用此 c++ 函数的 python 转换,检查非回文是最快的:

def is_palindrome2(l):
        left = 0
        right = len(l) - 1
        for i in xrange(0,len(l) / 2):
            if l[right] != l[left]:
                return False
            left += 1
            right -= 1
        return True

这是因为此函数在开始比较元素之前不会创建任何新列表。 请注意,我使用了第一个和最后一个元素不同的非回文,因此检查在第一次比较后立即返回(非回文并非总是如此)。

我还测试了 mistermiyagi (is_palindrome3) 和 PM 2Ring (is_palindrome4) 的答案:

import itertools

def create_palindrome(n):
    return range(1,n) + range(n,0,-1)

def is_palindrome1(l):
    return l[:len(l) // 2] == l[(len(l)+1) // 2:][::-1]

def is_palindrome2(l):
    left = 0
    right = len(l) - 1
    for i in xrange(0,len(l) / 2):
        if l[right] != l[left]:
            return False
        left += 1
        right -= 1
    return True

def is_palindrome3(seq):
    for l, r in itertools.izip(iter(seq), reversed(seq)):
        if l != r:
            return False
    return True

def is_palindrome4(seq):
    return all(l == r
               for _, l, r in itertools.izip(xrange((1 + len(seq)) // 2),
                                             iter(seq), reversed(seq)))

def is_palindrome5(l):
    return l == l[::-1]

if __name__ == '__main__':
    import timeit

    setup_palindrome = "from __main__ import create_palindrome, %s; l = create_palindrome(%i)"
    setup_non_palindrome = "from __main__ import %s; l=range(%i)"

def test(f, n):
    return (timeit.timeit("%s(l)" % f, setup=setup_palindrome % (f, n), number=100000),
            timeit.timeit("%s(l)" % f, setup=setup_non_palindrome % (f, n), number=100000))

small = 5
big = 1000

for f in is_palindrome1, is_palindrome2, is_palindrome3, is_palindrome4:
    print("%s small list: palindrome: %f non-palindrome: %f" % ((f.__name__,) + test(f.__name__, small)))
for f in is_palindrome1, is_palindrome2, is_palindrome3, is_palindrome4:
    print("%s big list: palindrome: %f non-palindrome: %f" % ((f.__name__,) + test(f.__name__, big)))

结果:

is_palindrome1 small list: palindrome: 0.093779 non-palindrome: 0.073669
is_palindrome2 small list: palindrome: 0.087658 non-palindrome: 0.048855
is_palindrome3 small list: palindrome: 0.085866 non-palindrome: 0.056385
is_palindrome4 small list: palindrome: 0.139685 non-palindrome: 0.123519
is_palindrome5 small list: palindrome: 0.021798 non-palindrome: 0.022422
is_palindrome1 big list: palindrome: 1.589591 non-palindrome: 0.432679
is_palindrome2 big list: palindrome: 9.414250 non-palindrome: 0.043481
is_palindrome3 big list: palindrome: 7.315568 non-palindrome: 0.056859
is_palindrome4 big list: palindrome: 6.655833 non-palindrome: 0.128915
is_palindrome5 big list: palindrome: 2.095099 non-palindrome: 0.283472

【讨论】:

  • 你介意给deque计时吗?
  • 感谢计时结果!您可能还想检查非回文。 ;)
  • @MisterMiyagi 添加了非回文
  • 虽然问题从一开始就被标记为 python-3.x,但这看起来是 Python 2 的答案 - l[:len(l) // 2] == l[-(len(l)//2):][::-1]
【解决方案2】:

这是 Miyagi 先生的答案的一个变体,它不会对每一对测试两次:

import itertools

def is_palindrome(seq):
    maxi = (1 + len(seq))//2
    for i, l, r in itertools.izip(xrange(maxi), iter(seq), reversed(seq)):
        #print l, r
        if l != r:
            return False
    return True

data = ('abcba', '123321', 'ABCDBA')
for seq in data:
    print seq, is_palindrome(seq)
    a = list(seq)
    print a, is_palindrome(a)

输出

abcba True
['a', 'b', 'c', 'b', 'a'] True
123321 True
['1', '2', '3', '3', '2', '1'] True
ABCDBA False
['A', 'B', 'C', 'D', 'B', 'A'] False

或者,作为一个班轮:

def is_palindrome(seq):
    return all(l == r
        for _, l, r in itertools.izip(xrange((1 + len(seq))//2), 
            iter(seq), reversed(seq)))

请注意,all() 短路,因此一旦检测到不匹配,它将立即退出。

【讨论】:

  • 您可能想使用any( l != r for ... 来获得额外的详细信息,但我想这是表现力、兼容性和速度的最佳解决方案。
【解决方案3】:

您可以采用与 C++ 代码相同的方式。

您有一个类节点,其中包含两个成员“数据”和“下一个”,您可以使用它们来制作自制的链表。

只使用对节点的引用而不是指向节点的指针。 使用包含一个 Node 元素的列表代替指向指针的指针。

下面是代码的相关部分

# Recursive call
isp = isPalindromeUtil(left, right.next);

# A wrapper over isPalindromeUtil()
def isPalindrome(head)
    return isPalindromeUtil([head], head)

# Move left to next node 
   left[0] = left[0].next

这样,它将像在 C++ 代码中一样工作,即当您“向左移动到下一个节点”时 在函数中,它也将为函数的调用者移动。

这不是进行递归调用的推荐方式。这个例子不是一个好的设计。它的评论也很糟糕:“如果子列表不是回文,则无需检查当前左右,返回false”。这是错误的。如果 list 是“ABCDEF”,递归的最里面的调用将测试 A 和 F,而不是 C 和 D。 它还会对所有内容进行两次测试:A-F、B-E、C-D、D-C、E-B、F-A,这并不是最有效的方法。

【讨论】:

    【解决方案4】:

    Python 没有像 C/C++ 这样的指针的概念,所以肯定没有双指针。但是,所有对象(除了基本类型之外的所有对象)都是引用(非常量)。如果你有一个 Node 类,你可以用非常相似的方式编写算法。

    【讨论】:

      【解决方案5】:

      如果要递归,可以对序列进行切片:

      def is_palindrome(seq):
          if len(seq) < 2:
              return True
          return seq[0] == seq[-1] and is_palindrome(seq[1:-1])
      

      但是,不鼓励在 Python 中使用递归。 Python doesn't support tail call optimisation, and probably never will.

      【讨论】:

      • 不应该是and is_palindrome(seq[1:-1])吗?
      • @Cipher 已修复,谢谢。这是我今天发布的第二个错误。 )c:
      • 正如那句老话:“计算机科学中只有两件难事:缓存失效、命名事物和非一错误。
      • 值得一提的是,Python 中应该避免递归(如果可行),因为 Python 没有尾调用优化。
      • @PM2Ring 和 C++ 一样吗?
      【解决方案6】:

      最高度优化的 Python 回文检查器版本如下:

      def is_palindrome(s):
          s = ''.join(s) # just in case a deque is passed
          return s==s[::-1]
      

      当然,我正在优化编写、调试和维护代码所需的时间。如果您想针对内存或处理时间进行优化,您可以做很多事情,包括用 C 或汇编语言编写它,但您应该在尝试之前绝对确定您确实需要这种优化。

      【讨论】:

      • 这实际上适用于python的双链表deque - 它不支持切片。
      • @MisterMiyagi - 我假设确切的数据结构是一个实现细节。如果我们正在处理一系列字符(这就是回文),那么在 Python 中表示它的最佳方式是使用字符串。如果由于某种原因你有一个字符的链接列表,你可以在将它发送到这个函数之前总是join()它。
      • 对不起,我很迂腐,但 OP 明确要求“检查给定的链表是否为回文”。我完全同意,对于一个字符串或列表,你的答案是要走的路,如果有人不能为额外的副本腾出内存,那么他就有另一个问题了。
      • @MisterMiyagi - 我添加了前面提到的琐碎的join()。现在它将处理双端队列。
      猜你喜欢
      • 2011-08-04
      • 2021-08-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-31
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多