最基本的几种线性结构为:
栈 stack
队列 Queue
双向队列 Dequeue
列表 List
链表 LinkedList
这几种线性结构的区别就是数据增减的方式。
栈 stack
栈是一种先进后出的线性结构。在栈中,数据项的添加和移除都发生在同一端,这一端叫做栈顶(top),另一端叫做栈底
距离栈底越近的数据留在栈中的时间越长,而最新加入栈的数据会被最先移除
这种次序叫做“后进先出LIFO” Last in First out
所以,需要在栈内保存时间长的就离栈底越近。
例如浏览器的后退功能,还有ctrl+z 撤销操作都是使用了栈的特性。
下面我们用 python 实现一个栈
# coding=utf-8
class Stack:
def __init__(self):
self.items=[]
def pop(self):
return self.items.pop()
def push(self,item):
self.items.append(item)
# 查看栈顶的元素
def peak(self):
return self.items[-1]
def size(self):
return len(self.items)
def isEmpty(self):
return self.items==[]
if __name__=="__main__":
s=Stack()
print(s.isEmpty())
s.push(4)
s.push("dog")
print(s.peak())
print(s.size())
print(s.pop())
print(s.pop())
print(s.size())
print(s.isEmpty())
上面将列表的尾部作为栈顶,首部作为栈底 pop和push 的复杂度都是O(1)
PS 假如你将列表的首部作为栈顶,尾部作为栈底
此时 push和pop 的实现变成如下所示
def pop(self):
return self.items.pop(0)
def push(self,item):
self.items.insert(0,item)
此时pop和push 的复杂度都是O(n)
栈的应用:
1. 简单括号匹配,判断一堆括号是否合法:
([(){}]) ,([]){} , [{[()]}] 这些是合法的
({)[}] , {() , ([[){[]} 这是不合法的。
思路如下:遇到一个左括号就就将左括号入栈,遇到右括号就将一个左括号出栈,判断这个左括号是否和右括号匹配。如果不匹配则这堆括号不合法。
# coding=utf-8
class Stack:
def __init__(self):
self.items=[]
def pop(self):
return self.items.pop()
def push(self,item):
self.items.append(item)
# 查看栈顶的元素
def peak(self):
return self.items[-1]
def size(self):
return len(self.items)
def isEmpty(self):
return self.items==[]
def isValidBrackets(brackets):
if len(brackets) % 2:
return False
left = ["[","(","{"]
right = ["]",")","}"]
stack = Stack()
for bracket in brackets:
# print(bracket)
if bracket in left:
stack.push(bracket)
elif stack.size(): # 如果此时遍历到右括号,而且栈中还有左括号
item = stack.pop()
index = left.index(item)
if right[index] != bracket:
return False
else: # 如果此时遍历到右括号,而且栈中没有左括号,说明左右括号的数量不相等或者右括号放在了左括号左边
return False
return True
if __name__=="__main__":
print(isValidBrackets("([(){}])"))
print(isValidBrackets("([]){}"))
print(isValidBrackets("[{[()]}]"))
print(isValidBrackets("({)[}]"))
print(isValidBrackets("({[)}]"))
print(isValidBrackets("({))}]"))
print(isValidBrackets("({)(}]"))
除了括号之外,还有html标签的匹配也可以这样判断
2. 十进制转为二进制
十进制转二进制的方法是不断除2取余数。
例如 35 求 2进制
35/2 = 17 ... 1
17/2 = 8 ... 1
8/2 = 4 ... 0
4/2 = 2 ... 0
2/2 = 1 ... 0
1/2 = 0 ... 1
将其翻转过来得到
二进制是 100011
def baseConverter(decNumber):
stack = Stack()
while decNumber:
stack.push(decNumber%2)
decNumber = math.floor(decNumber/2)
newString = ""
while not stack.isEmpty():
newString= newString + str(stack.pop())
return newString
if __name__=="__main__":
print(baseConverter(35))
下面将这个函数扩展为 十进制转2,8,16进制皆可的函数
def baseConverter(decNumber,base=2):
stack = Stack()
digits = "0123456789ABCDEF"
while decNumber:
stack.push(decNumber%base)
decNumber = math.floor(decNumber/base)
newString = ""
while not stack.isEmpty():
newString= newString + digits[stack.pop()]
return newString
if __name__=="__main__":
print(baseConverter(35,2))
print(baseConverter(35,8))
print(baseConverter(35,16))
3.表达式转换
中缀表达式 例如 A+B +在AB之间,所以+号是中缀表达式
由于 +-和*/ 的优先级不同,所以对于计算机而言,会对表达式加上括号从而知道其计算的优先级
A+B*C+D 就变成了 ((A+(B*C))+D)
这个叫做全括号表达式
内层括号优先级大于外层括号,所以计算机就知道先计算 B*C 然后是 A+xxx 最后才是 xxxx+D
但是对于计算机而言,这样还是有点复杂,于是计算机将中缀表达式变成后缀表达式:
A+B 这个中缀表达式变为后缀表达式是 AB+
A+B*C+D 是 ABC*+D+
怎么将一个复杂的中缀表达式变为后缀表达式,例如
(A+B)*C-(D-E)*(F+G)
其实只需要现将 中缀表达式 变为 全括号表达式,再将全括号表达式变为后缀表达式就会简单很多
(A+B)*C-(D-E)*(F+G) -> (((A+B)*C)-((D-E)*(F+G))) -> (((AB+)C*)((DE-)(FG+)*)-) ->AB+C*DE-FG+*-
后缀表达式的好处是:即使计算机不知道+和*的优先级,也可以按照从左到右的方式按顺序进行计算。
接下来,使用栈实现将中缀转为后缀
思路如下:
需要一个栈用来存放 左右括号和*/+-
需要一个列表用来存放操作数 ABCD等
将一个表达式从左到右逐个字符遍历,如果遍历到操作数则添加到列表
如果遍历到( 则压入栈
如果遍历到) 则反复弹出栈,并将栈内所有的+-*/逐一弹出栈并压入到列表中直到将对应的(给弹出来。左右括号不压入列表中
如果遍历到 +-*/ 则压入栈,但是压入之前先比较要压入的操作符与栈顶的操作符的优先级。
如果栈顶的高于或者等于它,则反复弹出栈顶的操作符(如果栈顶是括号则不弹出)压入列表直到栈顶的操作符优先级低于它,然后这个操作符入栈。
为什么栈顶的操作符的优先级等于遍历到的操作符也要弹出呢,因为虽然二者优先级相同但前者比后者的顺序在前,所以前者先执行。
当栈中的操作符弹出到优先级比遍历到的操作符时,栈不再弹出,此时遍历到的操作符要压入栈中,不能直接进入列表。因为后面的操作数要先于操作符进入列表。代码如下:
def inFixToPostfix(expr):
# 使用字典将操作符的优先级保存
priority = {
"+":2,
"-":2,
"*":3,
"/":3,
"(":1,
")":1
}
alpha = "QWERTYUIOPASDFGHJKLZXCVBNM"
brackets = ["(",")"]
stack = Stack()
postfix_list = []
for char in expr:
print(char)
if char in alpha:
postfix_list.append(char)
elif char == "(":
stack.push(char)
elif char == ")":
sign = stack.pop()
while sign!="(":
postfix_list.append(sign)
sign = stack.pop()
else:
while (not stack.isEmpty()) and priority[stack.peak()] >= priority[char]:
sign = stack.pop()
postfix_list.append(sign)
stack.push(char)
# 遍历完之后,如果栈里面还有操作符则要一一弹出并放到列表中
while not stack.isEmpty():
postfix_list.append(stack.pop())
newString=""
for char in postfix_list:
newString+=char
return newString
下面我们要将后缀表达式进行求值
(A+B)*C-(D-E)*(F+G) -> AB+C*DE-FG+*-
以这个为例,发现一个这样的规律:操作符只作用于离她最近的两个操作数。
可以使用栈求解,逻辑如下:
将操作数暂存到一个栈中,当遇到操作符时,将栈中离栈顶最近的两个操作数取出来进行运算,并将计算得到的值压入回栈中作为下一次运算的操作数。
直到最后遍历完所有的操作数和字符之后,栈中只剩下一个操作数,也就是最终的值。
此时,这里的栈只保存操作数,不保存操作符
假设 A~G 分别是 1~7 可以得到结果为 22代码如下:
def calcul(op,left,right):
if op=="+":
res = left+right
elif op=="-":
res = left-right
elif op=="*":
res = left*right
elif op=="/":
res = left/right
else:
res = False
return res
def calculPostfix(postfix,numDict):
sign = ["+","-","*","/"]
stack = Stack()
for char in postfix:
if char not in sign:
stack.push(numDict[char])
else:
right = stack.pop()
left = stack.pop()
stack.push(calcul(char,left,right))
return stack.pop()
if __name__=="__main__":
numDict = {
"A":1,
"B":2,
"C":3,
"D":4,
"E":5,
"F":6,
"G":7
}
print(calculPostfix("AB+C*DE-FG+*-",numDict))
上面为了方便,并没有验证后缀表达式是否合法,如果后缀表达式不合法是会报错的。
总结:如果使用到了反转的特性,就可以使用栈
队列 Queue
队列是一种线性结构,其特点是:数据的添加在一端(通常叫做尾端 rear),数据的移除在另一端(通常叫做首端 front),其实数据的添加不一定非在尾端,数据的移除不一定非在首端,但是数据的添加如果在尾端,那么移除就必须在首端,不能再同一端添加和移除。
--------------------------
添加数据-> 尾 Queue 首 ->移除数据
--------------------------
它的次序是先进先出(FIFO) first in first out
或者说先进先服务
队列只有一个入口和一个出口,不允许数据从队列中间加入或移除数据。
由于队列的先到先服务特性,所以队列适用于排队的场景
具体的应用场景例如
CPU调度进程
由于 CPU核数远少于进程数,有些进程还要等待不同类型的IO时间,所以将进程放到队列中排队循环运行。
下面我们实现一个队列:
功能如下:
unshift(item) 从首部加入队列
pop() 从尾部弹出队列
isEmpty() 是否为空
getSize() 返回元素个数
# coding=utf8
# 单向队列(列表实现,unshift头部添加复杂度O(n),pop尾部弹出复杂度O(1))
class ListQueue:
def __init__(self,aList = []):
self.queue = aList
def getSize(self):
return len(self.queue)
def unshift(self,item):
self.queue.insert(0,item)
def pop(self):
return self.queue.pop() if self.getSize() else None
def isEmpty(self):
return self.getSize() == 0
双向队列 Dequeue
双向队列由于有两端,所以栈和队列能做 到的双向队列都可以做到。它可以不具有内在的FIFO和LIFO的特性
下面使用python实现。
# 双向队列(列表实现,push O(n), pop O(1), unshift O(n), shift O(1))
class DoubleListQueue:
def __init__(self):
self.items=[]
def getSize(self):
return len(self.items)
def isEmpty(self):
return self.items == []
# 首部添加
def unshift(self,item):
self.items.insert(0,item)
# 尾部添加
def push(self,item):
self.items.append(item)
# 首部弹出
def shift(self):
return self.items.pop(0)
# 尾部弹出
def pop(self):
return self.items.pop()
上面这种实现的方式使用了 pop(0) 和 insert() 方法,这两种方法的复杂度是O(n)的
单向链表
单向链表包含这么几样东西:节点,指针(包含在节点内),首节点标记
其中指针其实是节点的一部分
单向链表的特点:
1. 链表的每一个元素(即节点)都会(通过指针)指向下一个元素,使得链表中的每一个元素单向的连接了起来,所以通过一个元素找到下一个元素。
2. 如果想查找链表中某一个节点的值,只能从第一个节点开始(通过指针)往下找。
3. 链表的首部标记指向一个节点。首部标记的节点就是链表的第一个节点。 查找链表中任何一个节点的值都是从首部节点开始往下找
4. 链表本身不包含任何数据和节点(只包含一个head首部标记记录着首部节点的引用),节点是分散的保存在不同的变量中的。
也就是说,链表对象中并不会创建一个容器来保存节点
5. 链表最后一个节点的指向为None
节点的特点:节点要包含两部分内容,节点的值和指针。指针保存的是另一个节点的引用
双向链表
除了单向链表之外,还有一种双向链表
双向链表在单向链表的基础上添加了一个尾部标记或者说尾部指针
而且双向链表中的节点也和单向链表的节点不同,双向链表的节点除了有一个向后指针 self.next 还有一个向前指针 self.previous
这样的话,单向链表是一个这样的结构
head
|
v
|---------| 指针 |---------| 指针 |---------| 指针
|key-value|----->|key-value|------>|key-value|------->Null
|---------| |---------| |---------|
而双向链表是一个这样的结构
head tail
| |
V V
Null<---|-----|<---|-----|<---|-----|<---|-----|
| k-v | | k-v | | k-v | | k-v |
|-----|--->|-----|--->|-----|--->|-----|--->Null
单向链表和双向链表对比:
单向链表只能从头部添加数据(或者说如果要写一个从尾部添加数据的方法,就要遍历整个链表所有节点,复杂度为O(n)),双向链表还可以从尾部添加数据(复杂度为O(1))
单向链表删除一个node复杂度为O(n),而双向链表是 O(1)
单向链表从尾部弹出一个节点复杂度为O(n),双向链表为O(1)
双向链表虽然很多方法的性能得到提升,但是是以消耗更多存储空间为代价实现的(体现在双向链表的节点比单向链表的节点多存储了一个向前指针)
单向链表和双向链表的实现如下:
# 链表
# 节点类
class Node:
def __init__(self,key,value):
self.key = key
self.value = value
self.next = None # 向后指针
self.prev = None # 向前指针
def getValue(self):
return self.value
def getKey(self):
return self.key
def getNext(self):
return self.next
def getPrev(self):
return self.prev
def setValue(self,value):
self.value = value
def setNext(self,node):
self.next = node
def setPrev(self,node):
self.prev = node
def __str__(self):
return str({'key':self.key,'value':self.value,'prev':self.prev.key if self.prev else None,'next':self.next.key if self.next else None})
# 单向链表(只能从头部添加,从尾部弹出)
class LinkedList:
def __init__(self):
self.head = None
self.size = 0 # 链表节点个数
# 从头部添加
def push(self,key,value):
node = Node(key,value)
if not self.isEmpty():
node.setNext(self.head)
self.head = node
self.size += 1
def pop(self):
prev = None # 当前节点的上一个节点
current = self.head #当前节点
if self.isEmpty():
return None
if self.size == 1:
self.head = None
else:
while current.getNext():
prev = current
current = current.getNext()
prev.setNext(None)
self.size -= 1
return current
# 根据key从链表中取出某节点
def get(self,key):
prev = None
current = self.head
while current:
if current.getKey() == key:
if prev: # prev不为None说明链表节点大于1
prev.setNext(current.getNext())
else:
self.head = None
self.size -= 1
return current
else:
prev = current
current = current.getNext()
return None
def getSize(self):
return self.size
def isEmpty(self):
return self.head == None
def __str__(self):
linkedList = {}
current = self.head
index = 0
while current:
linkedList[index] = {'key':current.key,'value':current.value,'prev':current.prev.key if current.prev else None,'next':current.next.key if current.next else None}
current = current.getNext()
index += 1
return str(linkedList)
# 双向链表(可从头部添加或弹出,也可从尾部添加或弹出)
class DoubleLinkedList:
def __init__(self):
self.head = None
self.tail = None
self.size = 0
# 尾部添加
def push(self,key,value):
node = Node(key,value)
if self.size == 0:
self.head = node
else:
self.tail.setNext(node)
node.setPrev(self.tail)
self.tail = node
self.size += 1
# 头部添加
def unshift(self,key,value):
node = Node(key,value)
if self.size == 0:
self.tail = node
else:
self.head.setPrev(node)
node.setNext(self.head)
self.size += 1
self.head = node
# 尾部弹出
def pop(self):
tail = self.tail
if self.size <= 1: # 0和1两种情况
self.head = None
self.tail = None
else:
prev = tail.getPrev()
tail.setPrev(None)
prev.setNext(None)
self.tail = prev
self.size = self.size - 1 if self.size > 0 else 0
return tail
# 头部弹出
def shift(self):
head = self.head
if self.size <= 1: # 0和1两种情况
self.head = None
self.tail = None
else:
next = head.getNext()
head.setNext(None)
next.setPrev(None)
self.head = next
self.size = self.size - 1 if self.size > 0 else 0
return head
# 获取特定key的节点
def get(self,key):
if self.size == 0:
return None
current = self.head
while current:
prev = current.getPrev()
next = current.getNext()
if current.key == key:
if self.head == current:
return self.shift() # shift 和 pop中已经有 self.size - 1的操作,请勿重复-1
if self.tail == current:
return self.pop()
self.size -= 1
prev.setNext(next)
next.setPrev(prev)
current.setNext(None)
current.setPrev(None)
return current
else:
current = next
return None
# 获取长度
def getSize(self):
return self.size
# 是否为空
def isEmpty(self):
return self.size == 0
def __str__(self):
linkedList = {}
current = self.head
index = 0
while current:
linkedList[index] = {'key':current.key,'value':current.value,'prev':current.prev.key if current.prev else None,'next':current.next.key if current.next else None}
current = current.getNext()
index += 1
return str(linkedList)
有了链表这种结构,我们就可以对队列进行改进使得队列的添加和弹出操作都是O(1) 的复杂度:
from .LinkedList import Node,DoubleLinkedList
# 改良单向队列(双向链表实现,unshift和pop都是O(1))
class LinkedListQueue(DoubleLinkedList):
def __init__(self, aList=[]):
super(LinkedListQueue, self).__init__()
self.init(aList)
def init(self,aList):
for index in range(len(aList)):
self.unshift(index,index)
def unshift(self,item):
super(LinkedListQueue, self).unshift(item,item)
def pop(self):
return super(LinkedListQueue, self).pop()
def shift(self):
raise Exception("Method shift is forbidden in class LinkedListQueue !")
def push(self):
raise Exception("Method push is forbidden in class LinkedListQueue !")
def get(self):
raise Exception("Method get is forbidden in class LinkedListQueue !")
# 改良双向队列(双向链表实现,unshift,shift,push和pop都是O(1))
class DoubleLinkedListQueue(DoubleLinkedList):
def __init__(self, aList=[]):
super(DoubleLinkedListQueue, self).__init__()
self.init(aList)
def init(self,aList):
for index in range(len(aList)):
self.unshift(index,index)
def unshift(self,item):
super(DoubleLinkedListQueue, self).unshift(item,item)
def pop(self):
return super(DoubleLinkedListQueue, self).pop()
def shift(self):
return super(DoubleLinkedListQueue, self).shift()
def push(self,item):
super(DoubleLinkedListQueue, self).push(item,item)
def get(self):
raise Exception("Method get is forbidden in class LinkedListQueue !")
队列的应用
回文词判定:
回文词就是例如 toot radar madam 这样的词
或者中文 “上海自来水来自海上”
用双向队列可以非常容易完成这个任务:将单词的字符一个个的从头部放入双相队列,放完之后,同时从两端取出一个字符,每次取出的两个字符进行判断是否相同,如果不同那就返回false
def isSymmetry(words):
queue = DoubleLinkedListQueue()
for word in words:
print(word)
queue.unshift(word)
while queue.getSize() > 1:
left = queue.shift().getValue()
right = queue.pop().getValue()
if left != right:
return False
return True
if __name__ == "__main__":
words = "上海自来水来自海上!"
print(isSymmetry(words))
接下来我们基于上面的双向链表创建一个 有序表
这个有序表的多出来的功能是,链表中是有顺序的,而且节点的顺序是按照数值的大小排序的。
例如
10 22 34 56 77 这就是一个有序表
22 10 56 77 34 就是一个无序表
# 有序列表(使用双向链表实现)
# 有序列表可以从尾部添加元素,从头部弹出元素,不能从中间加入元素
# 有序列表添加元素的时候会将元素放到链表中合适的位置使得整个链表中的元素是从小到大排列的
# 有序链表在双向链表的基础上添加一个search方法,能够判断链表中是否含有这个元素
class OrderList(DoubleLinkedList):
def pop(self):
raise Exception("Method pop is forbidden in class OrderList !")
def unshift(self,item):
raise Exception("Method unshift is forbidden in class OrderList !")
def push(self,item):
node = Node(item,item)
if self.size == 0:
self.head = node
self.tail = node
else:
current = self.tail # 定义一个当前指针,用于记录节点要插入的位置
isTail = True # 如果新添加的item就是链表中的最大值,则要将self.tail指向node
# 确定current,即确定要插入的位置
while current != None and current.getValue() > item:
current = current.getPrev()
isTail = False
# current有3种情况:current在首部(此时item是最小值); current在中间; current在尾部(此时item是最大值)
if current != None: # 当item是最中间值或最大值
afterNode = current.getNext()
node.setNext(afterNode)
node.setPrev(current)
current.setNext(node)
if not isTail: # item是中间值
afterNode.setPrev(node)
else: # item是最大值
self.tail = node
else: # item是最小值
node.setNext(self.head)
self.head.setPrev(node)
self.head = node
# 最后记得size加1
self.size += 1
# 判断链表是否有这个值
def search(self,item):
current = self.head
# 遍历即可
while current:
if current.getValue() == item:
return True
else:
current = current.getNext()
return False
# 从头部弹出时,不返回节点而直接返回元素值
def shift(self):
node = super(OrderList, self).shift()
return node.getValue() if node else None
def get(self,item):
raise Exception("Method get is forbidden in class OrderList !")
def __str__(self):
current = self.head
valList = []
while current:
valList.append(current.getValue())
current = current.getNext()
return str(valList)
本文转载自: 张柏沛IT技术博客 > 数据结构与算法python语言实现(二) 线性结构