1.顺序表
2.链表
3.栈
4.队列
线性结构是一种有序数据项的集合,其中每个数据项都有唯一的前驱和后继(除第一个没有前驱,最后一个没有后继)。不同线性结构的关键区别在于数据项增减的方式,有的结构只允许数据从一端添加,而有的结构则允许数据从两端移除。不同的方式形成了不同的线性结构,如栈(Stack)、队列(Queue)、双端队列(Deque)和列表(List)。
1. 顺序表
(1)连续存储与顺序表
计算机内部存储的本质是将是将所需要存储的数据类型转换为二进制表示,并存储在基本存储单元中(字节),1字节=8位,不同的数据类型所占用的内存大小不同,如32位计算机中,基本int占4个字节,char占1个字节。存储在基本单元中的数据都是0、1,需要通过存储时声明的数据类型,确认在取出时是当作int或char处理。
连续存储的概念是,定义一个有顺序的数据集合,如List = [2,54,13,31],在存储时(假设一个数字占4个字节),将各个数字转换为二进制数据,在创建List集合时,集合指向的内存地址为起始位置0x17,对于第0个元素则以0x17标记,由于一个数字占4个字节,那么下一个数字存储的内存地址为0x17+41=0x21,此后依次类推(起始地址+下标数据类型所占字节数)。
(2)顺序表的结构与实现*
顺序表在实现时,除了数据存储空间,一般还需要添加一个表头来存储顺序表的信息(顺序表内存多大、现在存储了多少)。顺序表实现的两种基本方式是一体式结构(表头和存储空间连续)和分离式结构(表头和存储空间分离,表头最后一个内存存储了指向存储空间的地址):
(3)顺序表的操作
①增加元素:
②删除元素时间复杂度同上
2. 链表
2.1 单链表
很明显,顺序表存在一个问题,在进行扩充时,如果当前内存不足,需要重新申请内存,将原内存存储迁移过去,这样会同时造成时间(复制)和空间(再次申请内存时预留)的浪费。那么能否实现这样一个表,保留顺序表索引的特性,同时,在扩充时,动态的申请内存直接添加呢?
(1)链表的结构
链表就能够实现这样的功能,存储一个元素的区域不再是一个元素的大小,还需要包含一个内存区域来存储指向下一个元素的地址:
(2)python中地址的指向
在python中,创建一个变量a = 10,a并不像是C一样是指存储10的内存的别名,也无法像C一样创建一个指针变量来指向一个地址。python中的变量名实际上保存的是指向保存10的内存的地址。所以在python中很方便的可以实现链表结构,通过直接创建一个变量即可作为指向一块内存的地址。
(3)链表的实现
通过python实现List,链表结构如下:
(4)单链表与顺序表的对比
①链表失去了顺序表随机读取的优点,同时由于链表还需要存储地址,空间开销大,但存储空间灵活;
②在删除和插入操作中,链表主要是遍历耗时,删除和插入都是O(1)的;而顺序表遍历查找很快,但拷贝覆盖耗时。
单向链表代码:
# coding:utf-8
class Node(object):
def __init__(self,elem = None):
self.elem = elem
self.next = None
class SingleLinkList(object):
"""single Link list"""
def __init__(self):
self.__head = None
def is_empty(self):
"""confirm whether the list is empty"""
return self.__head == None
def length(self):
"""the length of list"""
cur = self.__head
count = 0
while cur != None:
count += 1
cur = cur.next
return count
def travel(self):
"""travel the list,and print the every value of the list"""
print("[",end=" ")
cur = self.__head
while cur != None:
print(cur.elem,end = ", ")
cur = cur.next
print("]",end=" ")
def add(self,elem):
"""add element into the head of list"""
node = Node(elem)
node.next = self.__head
self.__head = node
def append(self,elem):
"""add element into the tail of list"""
node = Node(elem)
if self.is_empty():
self.__head = node
else:
cur = self.__head
while cur.next != None:
cur = cur.next
cur.next = node
def insert(self,pos,elem):
"""insert element into the specific index position of list"""
if pos <= 0:
self.add(elem)
elif pos > self.length() - 1:
self.append(elem)
else:
node = Node(elem)
pre = self.__head
count = 0
while count < pos - 1:
count += 1
pre = pre.next
node.next = pre.next
pre.next = node
def remove(self,elem):
"""remove a element that is found first"""
if self.is_empty():
return "empty list"
else:
cur = self.__head
pre = None
while cur != None:
if cur.elem == elem:
if cur == self.__head:
self.__head = cur.next
else:
pre.next = cur.next
cur.next = None
return
else:
pre = cur
cur = cur.next
def search(self,elem):
"""confirm whether the element is exist"""
if self.is_empty():
return "empty list"
else:
cur = self.__head
while cur != None:
if cur.elem == elem:
return True
else:
cur = cur.next
return False
if __name__ == "__main__":
ll = SingleLinkList()
print("the list is empty:",ll.is_empty())
print("the length of empty list:",ll.length())
ll.append(1)
print("the list is empty:",ll.is_empty())
print("the length of list:",ll.length())
ll.append(2)
ll.append(3)
ll.append(4)
ll.append(5)
ll.append(6)
print("---1 2 3 4 5 6---------------------")
print("---88 7 1 16 2 3 4 5 6---------------------")
ll.add(7)
ll.insert(-1,88)
ll.insert(3,16)
ll.travel()
print()
print("------------------------------")
print(ll.search(3))
ll.remove(16)
ll.travel()
print()
ll.remove(88)
ll.travel()
print()
ll.remove(6)
ll.travel()
2.2 单向循环链表
单向循环链表与单链表结构上的唯一区别就是尾结点不再指向None,而是指向头节点,形成一个闭环。
代码实现:
# coding:utf-8
class Node(object):
def __init__(self,elem = None):
self.elem = elem
self.next = None
class SingleLinkList(object):
"""single Link list"""
def __init__(self,node = None):
self.__head = node
if node:
node.next = node
def is_empty(self):
"""confirm whether the list is empty"""
return self.__head == None
def length(self):
"""the length of list"""
if self.is_empty():
return 0
cur = self.__head
count = 1
while cur.next != self.__head:
count += 1
cur = cur.next
return count
def travel(self):
"""travel the list,and print the every value of the list"""
print("[",end=" ")
if self.is_empty():
print("",end = "")
else:
cur = self.__head
while cur.next != self.__head:
print(cur.elem,end = ", ")
cur = cur.next
print(cur.elem,end = " ")
print("]",end = " ")
def add(self,elem):
"""add element into the head of list"""
node = Node(elem)
if self.is_empty():
self.__head = node
node.next = node
else:
cur = self.__head
while cur.next != self.__head:
cur = cur.next
node.next = self.__head
self.__head = node
cur.next = self.__head
def append(self,elem):
"""add element into the tail of list"""
node = Node(elem)
if self.is_empty():
self.__head = node
node.next = self.__head
else:
cur = self.__head
while cur.next != self.__head:
cur = cur.next
cur.next = node
node.next = self.__head
def insert(self,pos,elem):
"""insert element into the specific index position of list"""
if pos <= 0:
self.add(elem)
elif pos > self.length() - 1:
self.append(elem)
else:
node = Node(elem)
pre = self.__head
count = 0
while count < pos - 1:
count += 1
pre = pre.next
node.next = pre.next
pre.next = node
def remove(self,elem):
"""remove a element that is found first"""
if self.is_empty():
return "empty list"
else:
cur = self.__head
pre = None
while cur.next != self.__head:
if cur.elem == elem:
#head node
if cur == self.__head:
tail = self.__head
while tail != self.__head:
tail = tail.next
self.__head = cur.next
tail.next = self.__head
else:
#median node
pre.next = cur.next
cur.next = None
return
else:
pre = cur
cur = cur.next
#tail node
if cur.elem == elem:
if cur == self.__head:
self.__head = None
else:
pre.next = self.__head
cur.next = None
def search(self,elem):
"""confirm whether the element is exist"""
if self.is_empty():
return "empty list"
else:
cur = self.__head
while cur.next != self.__head:
if cur.elem == elem:
return True
else:
cur = cur.next
if cur.elem == elem:
return True
return False
if __name__ == "__main__":
ll = SingleLinkList()
print("the list is empty:",ll.is_empty())
print("the length of empty list:",ll.length())
ll.append(1)
print("the list is empty:",ll.is_empty())
print("the length of list:",ll.length())
ll.append(2)
ll.append(3)
ll.append(4)
ll.append(5)
ll.append(6)
print("---1 2 3 4 5 6---------------------")
print("---88 7 1 16 2 3 4 5 6---------------------")
ll.add(7)
ll.insert(-1,88)
ll.insert(3,16)
ll.travel()
print()
print("------------------------------")
print(ll.search(3))
ll.remove(16)
ll.travel()
print()
ll.remove(88)
ll.travel()
print()
ll.remove(6)
ll.travel()
2.3 双向链表
双向链表的节点,除头节点和尾节点,每个节点都会指向前驱节点和后继节点:代码实现
# coding:utf-8
class Node(object):
def __init__(self,elem = None):
self.elem = elem
self.next = None
self.prev = None
class DoubleLinkList(object):
"""single Link list"""
def __init__(self):
self.__head = None
def is_empty(self):
"""confirm whether the list is empty"""
return self.__head == None
def length(self):
"""the length of list"""
cur = self.__head
count = 0
while cur != None:
count += 1
cur = cur.next
return count
def travel(self):
"""travel the list,and print the every value of the list"""
print("[",end=" ")
cur = self.__head
while cur != None:
print(cur.elem,end = ", ")
cur = cur.next
print("]",end=" ")
def add(self,elem):
"""add element into the head of list"""
node = Node(elem)
node.next = self.__head
self.__head.prev = node
self.__head = node
def append(self,elem):
"""add element into the tail of list"""
node = Node(elem)
if self.is_empty():
self.__head = node
else:
cur = self.__head
while cur.next != None:
cur = cur.next
cur.next = node
node.prev = cur
def insert(self,pos,elem):
"""insert element into the specific index position of list"""
if pos <= 0:
self.add(elem)
elif pos > self.length() - 1:
self.append(elem)
else:
node = Node(elem)
cur = self.__head
count = 0
while count < pos:
count += 1
cur = cur.next
node.next = cur
node.prev = cur.prev
cur.prev.next = node
cur.prev = node
def remove(self,elem):
"""remove a element that is found first"""
if self.is_empty():
return "empty list"
else:
cur = self.__head
while cur != None:
if cur.elem == elem:
if cur == self.__head:
self.__head = cur.next
if cur.next:
cur.next.prev = None
cur.next = None
else:
if cur.next is None:
cur.prev.next = None
cur.prev = None
else:
cur.prev.next = cur.next
cur.next.prev = cur.prev
cur.next = None
cur.prev = None
return
else:
cur = cur.next
def search(self,elem):
"""confirm whether the element is exist"""
if self.is_empty():
return "empty list"
else:
cur = self.__head
while cur != None:
if cur.elem == elem:
return True
else:
cur = cur.next
return False
if __name__ == "__main__":
ll = DoubleLinkList()
print("the list is empty:",ll.is_empty())
print("the length of empty list:",ll.length())
ll.append(1)
print("the list is empty:",ll.is_empty())
print("the length of list:",ll.length())
ll.append(2)
ll.append(3)
ll.append(4)
ll.append(5)
ll.append(6)
print("---1 2 3 4 5 6---------------------")
print("---88 7 1 16 2 3 4 5 6---------------------")
ll.add(7)
ll.insert(-1,88)
ll.insert(3,16)
ll.travel()
print()
print("------------------------------")
print(ll.search(3))
ll.remove(16)
ll.travel()
print()
ll.remove(88)
ll.travel()
print()
ll.remove(6)
ll.travel()
2. 栈
栈(Stack)是一种有序的数据项集合,在栈中,数据项的加入和移除都发生在同一端(操作端叫栈顶)。距离栈底越近的数据,停留在栈中的时间越长,新加入的数据则会被最先移除,即后进先出
。栈相当于对链表(顺序表)进行了限制,不能任意删除、插入,而只能在一端删除添加。
通过python内置List实现栈功能:
# coding:utf-8
class My_stack(object):
def __init__(self):
self.__list = []
def push(self,item):
self.__list.append(item)
def pop(self):
return self.__list.pop()
def peek(self):
if self.__list:
return self.__list[-1]
else:
return None
def is_empty(self):
return self.__list == []
def size(self):
return len(self.__list)
if __name__ == "__main__":
s = My_stack()
print(s.is_empty())
s.push(1)
s.push(2)
s.push(3)
s.push(4)
s.push(5)
s.push(6)
print(s.size())
print(s.peek())
print(s.pop())
print(s.size())
(1)栈的应用:简单括号匹配
在数值计算或者代码中括号((
、{
、]
)都是成对出现的,且嵌套出现,如如果出现非成对或非嵌套形式将会导致出错,如()())
、(([)])
都是错误的括号匹配方式。那么如何实现括号的正确匹配呢?算法的流程是,从左往右扫描,遇到左括号就压入栈,遇到右括号则判断栈是否为空,不为空则移除栈顶数据,为空则匹配失败。当扫描完成后,判断栈空,栈空则匹配成功,栈不空则匹配失败。
# coding:utf-8
import sys
sys.path.append('./')
import my_stack as Stack
def par_checker(symbol_str):
s = Stack.My_stack()
balanced = True
index = 0
while index < len(symbol_str) and balanced:
symbol = symbol_str[index]
if symbol in "([{":
s.push(symbol)
elif symbol in ")]}" :
if s.is_empty():
balanced = False
else:
temp = s.peek()
if matches(temp,symbol):
s.pop()
else:
balanced = False
index += 1
if balanced and s.is_empty():
return True
else:
return False
def matches(open_s,close_s):
opens = list("([{")
closes = list(")]}")
return opens.index(open_s) == closes.index(close_s)
if __name__ == "__main__":
print(par_checker('(([({})]))'))
print(par_checker('((([)]))'))
print(par_checker('(5+3)*2+(2-(3+1))'))
(2)栈的应用:十进制转化为二进制
进制之间的转换就是将数字除以目标进制数,循环求余数,所有余数排列即为转化后的数字,先求出的余数在低位排列,类似栈的先进后出,所以通过栈来实现:
# coding:utf-8
import sys
sys.path.append('./')
import my_stack as Stack
def divide_by2(d_num):
remstack = Stack.My_stack()
while d_num > 0:
rem = d_num % 2
remstack.push(rem)
d_num = d_num // 2
binary_str = ""
while not remstack.is_empty():
binary_str = binary_str + str(remstack.pop())
return binary_str
if __name__ == "__main__":
print(divide_by2(42))
(3)栈的应用:表达式转换
通常我们看到的计算表达式是中缀
表示法,中缀表示法对于人来说比较好理解,但是如果不加括号、算符优先级问题,极其容易引起混淆。但对于计算机而言,最好是能明确规定所有计算顺序,而不用处理优先级问题。
所以,引入了前缀
和后缀
表示法,这两种方法只有一个规则:操作符只与其最近的两个操作数进行计算(前缀为符号及其邻近后两位操作数,后缀为前两位)。这两种方法不用考虑优先级问题。实现中缀表达式转化为前缀(后缀)表达式算法:
①首先将中缀表达式转换为全括号形式(如5+2*3+4
→ (5+(2*3)+4)
)
②按照下述方法移动操作符,并去除括号
转换为后缀表达式:
①创建一个栈来存储符号,从左向右读取每个字符(数字、字母、符号、括号)
②如果读取到字母或数字,直接追加到输出列表中
③如果读取到左括号,压入栈中
④如果读取到右括号:读取栈顶符号,如果不是左括号,就一直弹出栈中元素,直到栈顶为左括号
⑤如果是操作符:判断栈不为空时,栈顶操作符优先级是否大于等于当前读取到符号,如果大于等于,循环弹出栈顶优先级高的操作符;再将当前符号压入栈
⑥最后判断栈是否为空,不空则依次弹出栈,追加至输出列表
# coding:utf-8
import sys
sys.path.append('./')
import my_stack as Stack
def infix2postfix(infixexpr):
#define priorities
prec = {}
prec["*"] = 3
prec["/"] = 3
prec["+"] = 2
prec["-"] = 2
prec["("] = 1
op_stack = Stack.My_stack()
postfix_list = []
token_list = infixexpr.split()
# travel expression
for token in token_list:
if token in "ABCDEFGHIJKLMNOPQRSTUVWXYZ" or token in "0123456789":
postfix_list.append(token)
elif token == "(":
op_stack.push(token)
elif token == ")":
top_token = op_stack.pop()
while top_token != "(":
postfix_list.append(top_token)
top_token = op_stack.pop()
else:
while (not op_stack.is_empty()) and (prec[op_stack.peek()] >= prec[token]):
postfix_list.append(op_stack.pop())
op_stack.push(token)
while not op_stack.is_empty():
postfix_list.append(op_stack.pop())
return "".join(postfix_list)
if __name__ == "__main__":
s = "( ( ( A + 3 ) * B ) + ( 6 / C ) )"
ss = "A + 3 * C + 6 / C"
print(s)
print(ss)
print("---------")
print(infix2postfix(s))
print(infix2postfix(ss))
(4)栈的应用:后缀表达式求值
算法:从左向右扫描,如果是数字则压入栈,遇到符号则取出两个数字进行运算(先取出来的数字在算符右边),运算结果压回栈。
# coding:utf-8
import sys
sys.path.append("./")
import my_stack as Stack
def post_eval(postfix_expr):
op_num_stack = Stack.My_stack()
token_list = postfix_expr.split()
for token in token_list:
if token not in "*/+-":
token = int(token)
op_num_stack.push(token)
else:
op2 = op_num_stack.pop()
op1 = op_num_stack.pop()
result = math_op(token,op1,op2)
op_num_stack.push(result)
return op_num_stack.pop()
def math_op(op,op1,op2):
if op == "*":
return op1 * op2
elif op == "/":
return op1 / op2
elif op == "+":
return op1 + op2
elif op == "-":
return op1 - op2
if __name__ == "__main__":
ops = "32 4 + 6 * 7 8 / +"
print(post_eval(ops))
print("---------")
print("(((32+4)*6)+(7/8) = ",(((32+4)*6)+(7/8)))
3. 队列
队列(Queue)也是一种线性表,但只允许在一段插入,另一端进行删除的操作,即先进先出
。
(1)队列实现
# coding:utf-8
class Queue(object):
def __init__(self):
self.__list = []
def enqueue(self,item):
self.__list.append(item)
def dequeue(self):
return self.__list.pop(0)
def is_empty(self):
return self.__list == []
def size(self):
return len(self.__list)
if __name__ == "__main__":
q = Queue()
print(q.is_empty())
print(q.size())
q.enqueue(1)
q.enqueue(2)
q.enqueue(3)
q.enqueue(4)
q.enqueue(10)
print(q.dequeue())
print(q.size())
(2)双端队列实现
双端队列即允许两端插入和取出
# coding:utf-8
class Deque(object):
def __init__(self):
self.__list = []
def add_head(self,item):
self.__list.insert(0,item)
def add_tail(self,item):
self.__list.append(item)
def remove_head(self):
return self.__list.pop(0)
def remove_tail(self):
return self.__list.pop()
def is_empty(self):
return self.__list == []
def size(self):
return len(self.__list)
if __name__ == "__main__":
d = Deque()
print(d.is_empty())
print(d.size())
d.add_head(1)
d.add_tail(2)
d.add_head(3)
d.add_head(4)
d.add_tail(5)
d.add_tail(6)
print(d.remove_head())
print(d.size())
print(d.remove_tail())
print(d.size())
(3)队列的应用:热土豆
热土豆问题也叫约瑟夫问题:传烫手的热土豆,鼓声停止时,手里有土豆的人出列;如果去掉鼓,改为传过固定人数,就出列,即约瑟夫问题。也就是一圈人,某个人开始数数,假设数到7的人出列。通过队列的可以实现这样的功能,每次从队首取出一个人,并计数,没有计数为7的人从队尾入队列,计数为7则不入队列,一直循环,直到只剩下一个人。
# coding:utf-8
import sys
sys.path.append('./')
import Queue
def hot_patato(namelist,num):
name_queue = Queue.Queue()
for name in namelist:
name_queue.enqueue(name)
while name_queue.size() > 1:
for i in range(num):
name_queue.enqueue(name_queue.dequeue())
name_queue.dequeue()
return name_queue.dequeue()
if __name__ == "__main__":
ll = ["A","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S"]
print(hot_patato(ll,7))
(4)队列的应用:打印任务
当多人共享一台打印机时,采取先到先服务的队列策略来执行打印任务。在这种设定下,首要的问题是:打印作业系统的容量有多大?在能够接受的等待时间内,系统能容纳多少用户以多高频率提交多少打印任务?
问题背景设置:
对问题进行抽象:
①打印机属性:
i、私有字段:打印速度、当前是否有打印任务、当前任务所需剩余时间
ii、打印机功能(方法):执行打印1秒、打印机是否空闲、是否开始下个任务
②打印任务属性:
i、私有字段:生成打印任务的时间、打印任务页数
ii、任务方法:获取生成时间、获取打印页数、计算生成时间至开始打印本任务的等待时间
③打印流程模拟(设置打印时间范围和打印速度):
i、初始化打印机、打印队列、等待时间表
ii、在打印时间内,每1秒需要执行:
a、是否生成了打印任务,如果生成了,加入打印队列
b、打印机是否忙且队列是否为空,若打印机空闲且队列不空,则开始打印下一任务,同时计算这个任务在开始打印时间与生成这个任务时的时间差
c、执行1秒打印
注:
①打印任务的生成:一共10人,每小时每次提交2次任务,则每一秒提交任务的概率为20/3600=1/180;
②打印任务页数随机1-20,所以每次生成任务时,随机生成页数。
# coding:utf-8
import sys
sys.path.append('./')
import random
import Queue
class Printer:
def __init__(self,ppm):
self.__pagerate = ppm #print speed
self.__current_task = None
self.__time_remaining = 0
def tick(self):
if self.__current_task != None:
self.__time_remaining = self.__time_remaining - 1
if self.__time_remaining <= 0:
self.__current_task = None
def busy(self):
if self.__current_task != None:
return True
else:
return False
def start_next(self,new_task):
self.__current_task = new_task
self.__time_remaining = new_task.get_pages() * 60 / self.__pagerate
class Task:
def __init__(self,time):
self.__timestamp = time
self.__pages = random.randrange(1,21)
def get_stamp(self):
return self.__time_stamp
def get_pages(self):
return self.__pages
def wait_time(self,current_time):
return current_time - self.__timestamp
def new_print_task():
num = random.randrange(1,181)
if num == 111:
return True
else:
return False
def simulation_print(num_seconds,pages_per_minute):
lab_printer = Printer(pages_per_minute)
print_queue = Queue.Queue()
waiting_times = []
for current_time in range(num_seconds):
if new_print_task():
task = Task(current_time)
print_queue.enqueue(task)
if (not lab_printer.busy()) and (not print_queue.is_empty()):
next_task = print_queue.dequeue()
waiting_times.append(next_task.wait_time(current_time))
lab_printer.start_next(next_task)
lab_printer.tick()
ave_waiting_time = sum(waiting_times)/len(waiting_times)
print("Print task within %d hours,averge wait %6.2f secs,%3d tasks remaining"\
%(num_seconds/3600,ave_waiting_time,print_queue.size()))
if __name__ == "__main__":
print("ppm 10:")
for i in range(10):
simulation_print(3600,10)
print("ppm 5:")
for i in range(10):
simulation_print(3600,5)