4.2.4 实践演练——单链表结构字符串

下面的实例文件zifuchuan.py演示了实现单链表结构字符串的过程。

源码路径:daima\第4章\zifuchuan.py

(1)定义单链表字符串类string,对应实现代码如下所示。

class string(single_list):
     def __init__(self, value):
          self.value = str(value)
          single_list.__init__(self)
          for i in range(len(self.value)-1,-1,-1):
                self.prepend(self.value[i])
     def length(self):
          return self._num
     def printall(self):
          p = self._head
          print("字符串结构:",end="")
          while p:
                print(p.elem, end="")
                if p.next:
                    print("-->", end="")
                p = p.next
          print("")

(2)定义方法naive_matching()以实现匹配算法,返回匹配的起始位置,对应实现代码如下所示。

    def naive_matching(self, p):  #self为目标字符串,t为要查找的字符串
         if not isinstance(self, string) and not isinstance(p, string):
              raise stringTypeError
         m, n = p.length(), self.length()
         i, j = 0, 0
         while i < m and j < n:
              if p.value[i] == self.value[j]:#字符相同,考虑下一对字符
                   i, j = i+1, j+1
              else:                          #字符不同,考虑t中下一个位置
                   i, j = 0, j-i+1
         if i == m:                          #i==m,说明找到匹配,返回其下标
              return j-i
         return -1

(3)定义方法matching_KMP()以实现KMP匹配算法,返回匹配的起始位置,对应实现代码如下所示。

    def matching_KMP(self, p):
        j, i = 0, 0
        n, m = self.length(), p.length()
        while j < n and i < m:
            if i == -1 or self.value[j] == p.value[i]:
                 j, i = j + 1, i + 1
            else:
                 i = string.gen_next(p)[i]
        if i == m:
            return j - i
        return -1

(4)定义方法gen_next()以生成pnext表,对应实现代码如下所示。

    @staticmethod
    def gen_next(p):
        i, k, m = 0, -1, p.length()
        pnext = [-1] * m
        while i < m - 1:
            if k == -1 or p.value[i] == p.value[k]:
                 i, k = i + 1, k + 1
                 pnext[i] = k
            else:
                 k = pnext[k]
        return pnext

(5)定义方法replace(),把old字符串出现的位置换成new字符串,对应实现代码如下所示。

    def replace(self, old, new):
         if not isinstance(self, string) and not isinstance(old, string) \
                  and not isinstance(new, string):
              raise stringTypeError
         #删除匹配的旧字符串
         start = self.matching_KMP(old)
         for i in range(old.length()):
               self.delitem(start)
         #末尾情况下是用append操作追加的,顺序为正;而在前面的地方插入为前插;所以要分情况对待
         if start<self.length():
              for i in range(new.length()-1, -1, -1):
                    self.insert(start,new.value[i])
         else:
              for i in range(new.length()):
                    self.insert(start,new.value[i])
if __name__=="__main__":
     a = string("abcda")
     print("字符串长度:",a.length())
     a.printall()
     b = string("abcabaabcdabdabcda")
     print("字符串长度:", b.length())
     b.printall()
     print("朴素算法_匹配的起始位置:",b.naive_matching(a),end=" ")
     print("KMP算法_匹配的起始位置:",b.matching_KMP(a))
     c = string("xu")
     print("==")
     b.replace(a,c)
     print("替换后的字符串是:")
     b.printall()

上述解决方案有一个缺陷,在初始化字符串string对象时使用的是self.value=str(value);而在后面使用匹配算法时,无论是朴素匹配还是KMP匹配,都使用对象的value值来做比较。所以对象在实现replace()方法后的start=b.mathcing_KMP(a)后依旧不会发生变化,会一直为6。原因在于使用self.value进行匹配,所以replace()方法后的链表字符串里的值并没有被用到,从而发生严重的错误。执行后会输出:

字符串长度: 5
字符串结构:a-->b-->c-->d-->a
字符串长度: 18
字符串结构:a-->b-->c-->a-->b-->a-->a-->b-->c-->d-->a-->b-->d-->a-->b-->c-->d-->a
朴素算法_匹配的起始位置:6 KMP算法_匹配的起始位置:6
==
替换后的字符串是:
字符串结构:a-->b-->c-->a-->b-->a-->x-->u-->b-->d-->a-->b-->c-->d-->a

在下面的实例文件gaijin.py中,基于上面的实例文件zifuchuan.py进行改进,通过replace()实现字符串类的多次匹配操作。文件gaijin.py的主要实现代码如下所示。

源码路径:daima\第4章\gaijin.py

class string(single_list):
     def __init__(self, value):
          self.value = str(value)
          single_list.__init__(self)
          for i in range(len(self.value)-1,-1,-1):
                self.prepend(self.value[i])
     def length(self):
          return self._num
     #获取字符串对象值的列表,方便下面使用
     def get_value_list(self):
          l = []
          p = self._head
          while p:
               l.append(p.elem)
               p = p.next
          return l
     def printall(self):
          p = self._head
          print("字符串结构:",end="")
          while p:
               print(p.elem, end="")
               if p.next:
                    print("-->", end="")
               p = p.next
          print("")
    #朴素的串匹配算法,返回匹配的起始位置
    def naive_matching(self, p):  #self为目标字符串,t为要查找的字符串
         if not isinstance(self, string) and not isinstance(p, string):
              raise stringTypeError
         m, n = p.length(), self.length()
         i, j = 0, 0
         while i < m and j < n:
             if p.get_value_list()[i] == self.get_value_list()[j]:#字符相同,考虑下一对字符
                  i, j = i+1, j+1
             else:               #字符不同,考虑t中下一个位置
                  i, j = 0, j-i+1
         if i == m:              #i==m,说明找到匹配,返回其下标
             return j-i
         return -1
    #KMP匹配算法,返回匹配的起始位置
    def matching_KMP(self, p):
         j, i = 0, 0
         n, m = self.length(), p.length()
         while j < n and i < m:
              if i == -1 or self.get_value_list()[j] == p.get_value_list()[i]:
                   j, i = j + 1, i + 1
              else:
                   i = string.gen_next(p)[i]
         if i == m:
              return j - i
         return -1
    # 生成pnext表
    @staticmethod
    def gen_next(p):
         i, k, m = 0, -1, p.length()
         pnext = [-1] * m
         while i < m - 1:
              if k == -1 or p.get_value_list()[i] == p.get_value_list()[k]:
                   i, k = i + 1, k + 1
                   pnext[i] = k
              else:
                   k = pnext[k]
         return pnext
    #把old字符串出现的位置换成new字符串
    def replace(self, old, new):
         if not isinstance(self, string) and not isinstance(old, string) \
                  and not isinstance(new, string):
              raise stringTypeError
         while self.matching_KMP(old) >= 0:
              #删除匹配的旧字符串
              start = self.matching_KMP(old)
              print("依次发现的位置:",start)
              for i in range(old.length()):
                    self.delitem(start)
              #末尾情况下是用append操作追加的,顺序为正;而在前面的地方插入为前插;所以要分情况对待
              if start<self.length():
                  for i in range(new.length()-1, -1, -1):
                        self.insert(start,new.value[i])
              else:
                  for i in range(new.length()):
                        self.insert(start,new.value[i])
if __name__=="__main__":
     a = string("abc")
     print("字符串长度:",a.length())
     a.printall()
     b = string("abcbccdabc")
     print("字符串长度:", b.length())
     b.printall()
     print("朴素算法_匹配的起始位置:",b.naive_matching(a),end=" ")
     print("KMP算法_匹配的起始位置:",b.matching_KMP(a))
     c = string("xu")
     print("==")
     b.replace(a,c)
     print("替换后的字符串是:")
     b.printall()
     print(b.get_value_list())

其实上述方案依然有些缺陷,因为Python字符串对象是一个不变对象,所以replace()方法并不会修改原先的字符串,而只是返回修改后的字符串,而这个字符串对象是用单链表结构实现的,在实现replace()方法时改变了字符串对象本身的结构。执行后会输出:

字符串长度: 3
字符串结构:a-->b-->c
字符串长度: 10
字符串结构:a-->b-->c-->b-->c-->c-->d-->a-->b-->c
朴素算法_匹配的起始位置:0 KMP算法_匹配的起始位置:0
==
依次发现的位置: 0
依次发现的位置: 6
替换后的字符串是:
字符串结构:x-->u-->b-->c-->c-->d-->x-->u
['x', 'u', 'b', 'c', 'c', 'd', 'x', 'u']