数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。数据结构是带有结构特性的数据元素的集合,它研究的是数据的逻辑结构和物理结构以及它们之间的相互关系,并对这种结构定义相适应的运算,设计出相应的算法,并确保经过这些运算以后所得到的新结构仍保持原来的结构类型。 通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。
数据结构的三要素:
1.逻辑结构
2.物理结构(计算机里面存储方式)
3.运算操作
一、数据结构
1、基本概念
数据:描述客观事物的符号,是计算机中可以操作的对象,是能被计算机识别,并输入给计算机处理的符号集合。数据不仅仅包括整型、实型等数值类型,还包括字符及声音、图像、视频等非数值类型。
数据元素:数据元素是组成数据且有一定意义的基本单位,在计算机中通常作为整体处理。也被称为节点。
数据项:一个数据元素可以由若干个数据项组成。数据项是数据不可分割的最小单位。
数据对象:是性质相同的数据元素/节点的集合,是数据的子集。
数据 | |||||
数据对象 | |||||
数据元素/节点 | 数据元素/节点 | 数据元素/节点 | |||
数据项1 | 数据项2 | 数据项1 | 数据项2 | 数据项1 | 数据项2 |
代码中用结构体类型定义的变量叫数据元素,用该数据类型定义的数组叫数据对象,每一个字段叫数据项。
2、逻辑结构
逻辑结构:数据对象中元素/节点之间的相互关系
逻辑结构通常分为
①集合结构:数据元素/节点 除了同属于一个集合外,节点之间无其他关系
②线性结构:数据元素/节点之间是一对一的关系
③树形结构(层次结构)
数据元素/节点之间存在一对多的关系
④图形结构(网状结构)
3、物理结构
①顺序存储结构
把数据元素/节点存放在地址连续的存储单元里,这样的存储成为顺序存储结构。(数组、列表)
特点:内存连续,相对于链式存储结构每个元素占用的空间少
②链式存储结构
把数据元素/节点存放在任意的内存单元里。数据元素/节点之间的逻辑关系通过附设的指针域来表示,由此得到的存储表示成为链式存储结构
特点:内存不连续,通过指针链接
data:数据域,存放数据内容
next:指针域,存放下一个数据的地址
current:指针
# 定义一个类
class Node:
def __init__(self,data):
self.data = data
self.next = None
# 实例化对象
A = Node('A')
B = Node('B')
C = Node('C')
D = Node('D')
# 节点之间建立联系
A.next = B
B.next = C
C.next = D
# 测试链表
current = A # 将指针移动到A
while current: # 判断current中是否有内容来打印current中的data域
print(current.data)
current = current.next
③索引存储结构
在存储数据的同时,建立一个附加的索引表,即索引存储结构 = 数据文件+索引表
特点:检索速度快,多了一张索引表,故占用内存多,删除数据文件时要及时更改索引表
④散列存储结构
通过构造相应散列函数,由散列函数的值来确定数据元素/j节点存放的地址
特点:存的时候按照对应关系存储,取的时候按照对应关系取出
逻辑结构是面向问题的,物理结构是面向计算机的。其基本目标是将数据及其逻辑关系存储到计算机的内存中。
4、运算操作
增删改查
二、算法
1、概念
软件 = 程序 + 文档
程序 = 数据结构 + 算法
软件 = 数据结构 + 算法 + 文档
算法 = 对结点集合的运算和操作 + 控制结构
算法用来描述对特定问题的求解步骤,它是指令的有限序列,其中每一条指令代表一个或多个操作。
2、特征
算法五大特征:
1.有穷性:一个算法必须总是在执行有穷步以后结束,且每一步都可以在有穷的时间内完成。
2.确切性:算法中每一个指令都必须有确切的含义,保证读者和计算机在理解时不会产生二义性。
3.可行性:一个算法是能行的,即算法中描述的操作都是可以通过执行有效次基本运算来实现。
4.输入性:一个算法有零个输入或多个输入,以刻画运算对象的初始情况。
5.输出性:一个算法必须有一个输出或多个输出,以反应出对输入数据加工后的结果,没有输出的算法是毫无意义的。
3、描述
算法的描述形式多种多样,不同的描述形式对算法的质量有一定的影响。描述同一个算法可以采用自然语言、流程图、盒图、伪代码以及程序设计语言等,常用的描述算法方法有如下四种。
1.自然语言描述法
最简单的描述算法的方法是使用自然语言,用自然语言来描述算法的优点是简单且便于人们对算法的理解和阅读,缺点是不够严谨,易产生歧义。
2.算数框图法
使用程序流程图、盒图等算法描述工具来描述算法。其特点是简洁明了、便于理解和交流。
3.伪代码语言描述法
为了解决理解与执行之间的矛盾,人们常常使用一种称为伪代码语言的描述方法来对算法进行描述。伪代码语言介于高级程序设计语言和自然语言之间,它忽略高级程序设计语言中一些严格的语法规则与描述细节,因此它比程序设计语言更容易描述和被人理解,而比自然语言或算法框图更接近程序设计语言。
4.高级程序设计语言描述法
使用特定的可以直接在计算机上执行的程序描述算法。优点是不用转换直接可以编译执行,缺点是需要对特定的程序设计语言比较理解。大部分的算法最终是需要通过能够向计算机发送一系列命令的程序来实现的。
4、标准
衡量一个算法的好坏的四个标准:正确性、可读性、健壮性、高效率和低存储量的特征。
1.正确性:算法至少应该具有输出、输出和加工处理无歧义性、能正确反映问题的需求、能够得到问题的正确答案。
2.可读性:便于阅读、理解和交流。
3.健壮性:当输入数据不合法时,算法也能做出相关处理,而不是产生异常或莫名其妙的结果。
4.时间效率高与低存储量需求:时间效率指算法的执行时间,存储量主要指算法程序运行时所占用的内存或外部硬盘空间。设计算法应该尽量班组时间效率高和存储量低的要求。
5、时间复杂度
①前言
对于一个算法的复杂性分析主要是对算法效率的分析,包括衡量其运行速度的时间效率,以及其运行时所需要占用的空间大小。对于算法的时间效率的计算,通常是抛开与计算机硬件、软件有关的因素,仅考虑实现该算法的高级语言程序。
算法的时间复杂度是一个函数,它定性描述该算法的运行时间,时间复杂度常用 O 表述,它衡量着一个程序的好坏,时间复杂度的估算是算法题的重中之重。
②概念
要想计算时间复杂度首先得找到该算法中的循环,算法中循环执行的次数就是算法的时间复杂度。
通常时间复杂度用一个问题规模函数来表达:T(n) = O(f(n))
T(n) 问题规模的时间函数
n 代表的是问题的规模 输入数据量的大小
O 时间数量级
f(n) 算法中可执行语句重复执行的次数
③计算
时间复杂度的计算
例1
def func(N):
num = 0
for i in range(N):
for j in range(N):
num += 1
for k in range(N):
num += 1
for l in range(10):
num += 1
上述程序中一共有N*N + N + 10次循环
计算时间复杂度有四步骤
由此可得计算时间复杂度的一般规律(大O表示法)
N*N+N+10
如果有常数项将其置为
1
。N*N+N+1
去除表达式中所有加法常数。
N*N+N
修改的表达式中 只保留最高阶项,因为只有它才会对最终结果产生影响。
N*N
如果最高阶项系数存在且不是
1
,则将其系数变为1
,得到最后的表达式。N*N
所以时间复杂度表达为 T(n) = O(N*N)
例2
def fun(n):
num = 0
for i in range(100):
num += 1
return num
上例中程序中,不过n多大,程序都只执行100次,记过四个步骤可知时间复杂度为O(1)
三、线性表
1、顺序表(内存连续)
线性表的顺序存储结构实现的是一组连续的存储单元线性表的数据元素。
①概念
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性表,一般情况下采用数组(列表)存储。在列表上完成数据的增查改删
逻辑结构:线性结构
物理结构:顺序存储结构
线性表的特点:一对一,每个节点最多一个前驱和一个后继,头尾节点特殊:首节点无前驱,尾节点无后继
②顺序表(练习:自己实现insert( )思想)
要求:定义两个函数:
通过定义一个类来编写我们的insert函数整体
class Insert:
N = 32 # 用来记录顺序表的最大容量,可以改变
def __init__(self):
self.last = -1 # 定义一个last来记录顺序表中的最后一个有效元素的下标
self.data = [0] * Insert.N # 顺序表的数据存储
def is_full(self): # 定义一个判断顺序表是否满了的函数
return Insert.N == self.last + 1 # 当最后一个有效元素的下标+1 = 顺序表的容量时,顺序表已满
def is_empty(self):
return Insert.N == -1 # 当最后一个有效元素的下标=-1时,代表了顺序表中没有元素(下标从0开始)
def insert(self,index:int,data): # 插入函数
if self.is_full() or index < 0 or index > self.last + 1: # 判断一些无法插入情况
print("insert error")
return
for i in range(self.last,index - 1, -1):
self.data[i+1] = self.data[i] # 倒插,将前一个下标的元素挪到后一个下标,实现插入功能
self.data[index] = data # 将要插入的位置的元素改为要输入的数据
self.last += 1 # 插入一个数据,最大有效元素的下标也随之向后移一位
def delete(self,index): #删除函数
if self.is_empty() or index < 0 or index > self.last: # 判断一些无法删除的情况
print("delete error")
return
for i in range(index,self.last): # 遍历要删除的位置到最后
self.data[i] = self.data[i+1] # 将后一个位置的内容挪给前一个位置
self.last -= 1 # 删除一个数据随之最大有效元素的下标减1
def search(self,data): # 查找函数
if self.is_empty(): # 判断一些无法查询的情况
print("search error")
return
for i in range(self.last+1): # 从顺序表的第一个元素遍历到结尾
if self.data[i] == data: # 如果查询到要查询的元素
return i # 返回这个元素的下标
return -1 # 找不到这个元素返回-1
def show(self): # 输出函数
for i in range(self.last+1): # 遍历顺序表
print(self.data[i],end=" ") # 打印每一个元素
print()
def change(self, index: int, data: int): # 修改函数
if self.is_empty() or index < 0 or index > self.last: # 判断一些无法改变的情况
print("change error")
return
self.data[index] = data # 通过下标改变下标位置的值
def clear(self): # 清除函数
self.last = -1 # 当最大有效元素的下标为-1时也就是让顺序表为空
if __name__ == '__main__':
i = Insert() # 调用类
i.insert(0,1)
i.insert(1,11)
i.insert(2,22)
i.insert(3,33)
i.insert(4,44)
i.insert(5,55)
i.show()
i.delete(2)
i.show()
print(i.search(55))
2、链表
链表就是将结点用链串起来的线性表,链就是结点中的引用
链表分为单向链表、单向循环链表、双向链表。双向循环链表
能够解决插入和删除麻烦以及长度固定问题
1.逻辑结构:线性结构
2.物理结构:链式结构
①单向链表
单向链表分为 有头单向链表 和 无头单向链表
无头单向链表
# 无头单向链表
class Node:
def __init__(self, data):
self.data = data
self.next = None
A = Node('A')
B = Node('B')
C = Node('C')
D = Node('D')
A.next = B
B.next = C
C.next = D
current = A
while current:
print(current.data)
current = current.next
有头单项链表
# 有头单向链表
class Node:
def __init__(self, data = None,next_node= None):
self.data = data
self.next = next_node
head = Node()
A = Node("A")
B = Node("B")
C = Node("C")
D = Node("D")
head.next = A
A.next = B
B.next = C
C.next = D
current = head
while current.next:
current = current.next
print(current.data)
有头链表中的第一个头节点的数据域无效,但引用有效。在对有头单链表进行操作之前,需要对其进行初始化,即创建一个空的有头单链表。
无头链表中的每一个节点的数据域和引用域都是有效的
有头单向链表的操作函数
思想(先连后面,再连前面):
先遍历找到要插入节点的前一个节点,假如上一个节点为A,下一个节点为B,将C插入A与B
先让C链接B,再让A链接C。
实现:第一步,创建一个类用来创建链表的节点
# 有头单向链表
class Node:
def __init__(self, data = None,next_node= None):
self.data = data
self.next = next_node
第二步:创建一个类用来写整体函数
class Linkedlist:
def __init__(self):
self.head = Node() # 创建一个头节点
第三步:写具体函数
判断能否进行操作的函数
def is_empty(self): # 判断链表是否为空
return self.head.next is None # 如果头节点的下一个节点为空,说明链表为空
def length(self): # 计算链表的长度
length = 0
current = self.head # 将current指针移动头节点上
while current.next: # 循环链表计算长度
length += 1
current = current.next
return length
def insert(self,position : int,data): # 插入函数
if position < 0 or position > self.length(): # 判断一些不合理插入行为
print("insert error")
return
current = self.head # 将current指针移动到头节点上
new_node = Node(data) # 通过Node创建插入的数据
for i in range(position): # 通过for循环将current 指针移动到要插入的位置的前一项
current = current.next
# 插入数据的指针域链接后一项的地址,在插入数据前,后一项的地址存储在前一项的指针域中,
# 所以让插入项的指针域获取后一项的地址(即前一项的指针域)
# 先连后面,而不是先连前面,是为了防止先连好前面反而丢失了存储在前一项中的后面的数据
new_node.next = current.next
# 再让前一项的指针域链接插入项的地址,完成前后链接
current.next = new_node
def delete_position(self,position:int): # 删除位置函数
if self.is_empty() or position < 0 or position >= self.length(): #不合理删除行为
print("delete position error")
return
current = self.head # 将指针移到头节点上
for i in range(position): # 通过for循环将指针移动到要删除的位置的前面
current = current.next
# 假如有ABC A.next = B B.next = C,若要删除B,我们要引用B中存储的C的地址
# 即 (A.next).next = C 要链接A和C 即 A.next = A.next.next
current.next = current.next.next
def search(self,data): # 查找函数
if self.is_empty(): # 判断链表是否为空再去查找
print("search error")
return
search_position = 0 # 定义一个变量用来计算下标
current = self.head # 将指针移到头节点上
while current.next: # 如果指针所在的项的指针域中不为空,进入循环
current = current.next # 指针向下一个位置走一步
if current.data == data: # 如果指针当前项存储的数据与要查询的数据相等,返回位置
return search_position
search_position += 1 # 计算下标
return -1 # 如果不存在.返回-1
def delete_data(self,data): # 删除数据函数
if self.is_empty() or self.search(data) == -1: # 判断是否为空及删除的数据存不存在
print("delete data error")
return
current = self.head # 指针移动在链表头节点上
while current.next: # 如果指针下一项存在
if current.next.data == data: # 如果指针下一项的数据与要删除的数据相同
current.next = current.next.next # 跳过下一项,链接指针当前项与下下项
else: # 如果指针下一项数据与要删除的数据不相同
current = current.next # 指针向后走一步
def change(self,position,data): # 修改函数
if self.is_empty() or position < 0 or position >= self.length(): # 判断
print("change error")
return
current = self.head # 将指针移动到头节点上
for i in range(position+1): # for 循环到position位置
current = current.next
current.data = data # 将position位置的数据修改为data
def reverse(self): # 转置函数
current = self.head.next # 将指针移动到头节点的下一项(第一项),保存节点地址
self.head.next = None # 让头节点存储的地址变为空,断开链表
# 此时有头单项链表被分为无头单项链表和一个有头空链表
while current: # 遍历无头单向链表,把无头单项链表的节点头插到空链表中
next_node = current.next # 提前将current的下一个节点保存起来
current.next = self.head.next # 先连后面,再连前面
self.head.next = current # 将无头表的节点插入头节点的下一个位置
current = next_node # 移动current,指向下一个节点
def clear(self): # 清空链表
self.head.next = None
def show(self):
current = self.head
while current.next:
current =current.next
print(current.data,end=" ")
print()
总链表代码
# 有头单向链表
class Node:
def __init__(self, data = None,next_node= None):
self.data = data
self.next = next_node
class LinkedList:
def __init__(self):
self.head = Node() # 创建一个头节点
def is_empty(self): # 判断链表是否为空
return self.head.next is None # 如果头节点的下一个节点为空,说明链表为空
def length(self): # 计算链表的长度
length = 0
current = self.head # 将current指针移动头节点上
while current.next: # 循环链表计算长度
length += 1
current = current.next
return length
def insert(self,position : int,data): # 插入函数
if position < 0 or position > self.length(): # 判断一些不合理插入行为
print("insert error")
return
current = self.head # 将current指针移动到头节点上
new_node = Node(data) # 通过Node创建插入的数据
for i in range(position): # 通过for循环将current 指针移动到要插入的位置的前一项
current = current.next
# 插入数据的指针域链接后一项的地址,在插入数据前,后一项的地址存储在前一项的指针域中,
# 所以让插入项的指针域获取后一项的地址(即前一项的指针域)
# 先连后面,而不是先连前面,是为了防止先连好前面反而丢失了存储在前一项中的后面的数据
new_node.next = current.next
# 再让前一项的指针域链接插入项的地址,完成前后链接
current.next = new_node
def delete_position(self,position:int): # 删除位置函数
if self.is_empty() or position < 0 or position >= self.length(): #不合理删除行为
print("delete position error")
return
current = self.head # 将指针移到头节点上
for i in range(position): # 通过for循环将指针移动到要删除的位置的前面
current = current.next
# 假如有ABC A.next = B B.next = C,若要删除B,我们要引用B中存储的C的地址
# 即 (A.next).next = C 要链接A和C 即 A.next = A.next.next
current.next = current.next.next
def search(self,data): # 查找函数
if self.is_empty(): # 判断链表是否为空再去查找
print("search error")
return
search_position = 0 # 定义一个变量用来计算下标
current = self.head # 将指针移到头节点上
while current.next: # 如果指针所在的项的指针域中不为空,进入循环
current = current.next # 指针向下一个位置走一步
if current.data == data: # 如果指针当前项存储的数据与要查询的数据相等,返回位置
return search_position
search_position += 1 # 计算下标
return -1 # 如果不存在.返回-1
def delete_data(self,data): # 删除数据函数
if self.is_empty() or self.search(data) == -1: # 判断是否为空及删除的数据存不存在
print("delete data error")
return
current = self.head # 指针移动在链表头节点上
while current.next: # 如果指针下一项存在
if current.next.data == data: # 如果指针下一项的数据与要删除的数据相同
current.next = current.next.next # 跳过下一项,链接指针当前项与下下项
else: # 如果指针下一项数据与要删除的数据不相同
current = current.next # 指针向后走一步
def change(self,position,data): # 修改函数
if self.is_empty() or position < 0 or position >= self.length(): # 判断
print("change error")
return
current = self.head # 将指针移动到头节点上
for i in range(position+1): # for 循环到position位置
current = current.next
current.data = data # 将position位置的数据修改为data
def reverse(self): # 转置函数
current = self.head.next # 将指针移动到头节点的下一项(第一项),保存节点地址
self.head.next = None # 让头节点存储的地址变为空,断开链表
# 此时有头单项链表被分为无头单项链表和一个有头空链表
while current: # 遍历无头单向链表,把无头单项链表的节点头插到空链表中
next_node = current.next # 提前将current的下一个节点保存起来
current.next = self.head.next # 先连后面,再连前面
self.head.next = current # 将无头表的节点插入头节点的下一个位置
current = next_node # 移动current,指向下一个节点
def clear(self): # 清空链表
self.head.next = None
def show(self):
current = self.head
while current.next:
current =current.next
print(current.data,end=" ")
print()
if __name__ == '__main__':
list1 = LinkedList()
list1.insert(0,1)
list1.insert(1,11)
list1.insert(2,22)
list1.insert(3,33)
list1.insert(4,44)
list1.insert(5,55)
list1.show()
list1.delete_position(2)
list1.show()
list1.delete_data(33)
list1.show()
②单向循环链表
约瑟夫问题
设编号为1
,2
,……n
的n
个人围坐一圈,约定编号为k (1≤k≤n)
的人从1
开始报数,数到m
的那个人出列。它的下一位继续从1
开始报数,数到m
的人出列,依次类推,剩下最后一个。
假设n=6
总共6人,k=1
从第一个人开始,m=5
,每次从1
数到5
。
第一轮报数:从1
号开始,数5
个数,数完5
个数,5
号出列,第一轮报数后,剩余人数如下。
第二轮报数:k = 1, m = 5
第三轮:k = 1, m = 5
第四轮:k = 1, m = 5
第五轮: k = 1, m = 5
class Node:
"""创建单链表节点"""
def __init__(self, data=None):
self.data = data
self.next = None
def Josepy(self):
all_num = 6 # 总数
start_num = 1 # 从几号开始数
kill_num = 5 # 数到几号出列
head = Node(1)
p_tail = head # 尾指针指向当前的第一个结点
for i in range(2, all_num + 1):
new_node = Node(i) # 实例化新结点且装上数据
p_tail.next = new_node # 链接到链表的尾
p_tail = new_node # 尾指针继续指向当前链表的尾
p_tail.next = head # 形成单向循环链表
# 开始
# 将头指针移动到开始的号码处
for i in range(0, start_num - 1):
head = head.next
# 循环出列
while head != head.next:
for i in range(0, kill_num - 2):
head = head.next
p_del = head.next
# 跨过要删除的节点
head.next = p_del.next
print("kill is -------------=", p_del.data)
# 出列后,从下一个节点开始继续开始数, 将头指针移动到开始数的地方
head = head.next
print("king is=================== ", head.data)
if __name__ == '__main__':
linklist = Node()
linklist.Josepy()
3、栈
栈:只能在一端进行插入和删除操作的线性表(又称为堆栈),进行插入和删除操作的一端称为栈顶,另一端称为栈底。
①顺序栈
概念
顺序栈特点:先进后出FILO first in last out
泡腾片就是个形象的栈,只能先取出上面的,才能取下面的
top始终标记栈顶(始终代表最后一个有效元素的下标)
逻辑结构:线性结构
物理结构:顺序存储
接口实现
class Stack:
max_len = 32 # 保存栈的最大长度
# 始终代表当前栈内最后一个有效元素的下标,称为栈顶指针
top = -1
data = [0] * max_len # 顺序栈的存储空间
# 1、判断是否为满, 满返回True,未满返回False
def is_full(self):
pass
# 2. 入栈
def push(self, data: int): # data代表入栈的数据
pass
# 3.判断栈是否为空
def is_empty(self):
pass
# 4.出栈
def pop(self):
pass
# 5. 清空栈
def clear(self):
pass
# 6.获取栈顶数据(注意不是出栈操作,如果出栈,相当于删除了栈顶数据,只是将栈顶的数据获取到,不需要移动栈针)
def get_top(self):
pass
# 7. 求栈的有限长度
def get_length(self):
pass
判断函数
# 1.判断栈是否为空
def is_full(self):
return self.top + 1 == Stack.max_len
# 2.判断栈是否为空
def is_empty(self):
return self.top == -1
入栈函数
# 入栈函数
def push(self,data):
if self.is_full():
print("stack is full")
return
self.top += 1
self.data[self.top] = data
出栈函数
# 4.出栈
def pop(self):
if self.is_empty():
print("stack is empty")
return
self.top -= 1
return self.data[self.top+1]
其他函数
def length(self):
return self.top + 1
def get_top(self):
if self.is_empty():
print('get top error')
return
return self.data[self.top]
def clear(self):
self.top = -1
顺序栈总代码
class Stack:
max_len = 32
top = -1
data = [0] * max_len
def is_full(self):
return self.top + 1 == self.max_len
def is_empty(self):
return self.top == -1
def push(self,data):
if self.is_full():
print("Stack is full")
return
self.top += 1
self.data[self.top] = data
def pop(self):
if self.is_empty():
print("Stack is empty")
return
self.top -= 1
return self.data[self.top+1]
def length(self):
return self.top + 1
def get_top(self):
if self.is_empty():
print('get top error')
return
return self.data[self.top]
def clear(self):
self.top = -1
if __name__ == '__main__':
s = Stack()
s.push(1)
s.push(2)
s.push(3)
s.push(4)
print(s.length())
print(s.get_top())
s.pop()
②链式栈
概念
链式栈LinkStack
逻辑结构:线性结构
物理结构:链式存储
栈的特点:后进先出
栈顶是出栈和入栈的地方,链式栈的入栈和出栈都是通过对链表进行头插尾删来实现。
选用无头单向链表来写链式栈,对于链式栈来说,不存在栈满的情况(内存不够除外)
空栈时top == None
接口实现
class Node:
"""链式栈节点类"""
def __init__(self, data):
self.data = data
self.next = None
class LinkStack:
def __init__(self):
self.top = None
# 2.入栈,data是入栈的数据
def push(self, data):
pass
# 3.判断栈是否为空
def is_empty(self):
pass
# 4.出栈
def pop(self):
pass
# 5.清空栈
def clear(self):
pass
# 6.求栈的长度
def length(self):
pass
# 7.获取栈顶数据,不是出栈,不需要移动top
def get_top(self):
pass
if __name__ == '__main__':
入栈
栈顶指针top始终指向无头单项链表的头,栈空除外
def push(self, data):
new_node = Node(data)
new_node.next = self.top
self.top = new_node
出栈 需要判空
def is_empty(self):
return self.top is None
def pop(self):
if self.is_empty():
print("pop failed")
return
data = self.top.data
self.top = self.top.next
return data
获取栈顶数据
def get_top(self):
if self.is_empty():
print("get_top failed")
return
return self.top.data
清空栈
def clear(self):
self.top = None
整体代码
class Node:
def __init__(self, data):
self.data = data
self.next = None
class LinkStack:
def __init__(self):
self.top = None
def push(self, data):
new_node = Node(data)
new_node.next = self.top
self.top = new_node
def is_empty(self):
return self.top is None
def pop(self):
if self.is_empty():
print("pop failed")
return
data = self.top.data
self.top = self.top.next
return data
def clear(self):
self.top = None
def length(self):
current = self.top
len = 0
while current:
len += 1
current = current.next
return len
def get_top(self):
if self.is_empty():
print("get_top failed")
return
return self.top.data
if __name__ == '__main__':
link_stack = LinkStack()
link_stack.push(1)
link_stack.push(2)
link_stack.push(3)
link_stack.push(4)
link_stack.push(5)
print(link_stack.get_top())
print(link_stack.pop())
print(link_stack.length())
4、队列
①顺序队列(循环队列)
特点:先进先出
逻辑结构:线性结构
物理结构:顺序存储结构
引用两个指针front和rear分别指向队列头元素和队尾的下一个元素,rear指向空占一个位置,当front等于rear时,表示空队列
假溢出:后面入列直至空间满,而前面出列使前面空间空出而没有填充。可以通过头尾相接的循环解决就假溢出
判空条件rear == front 当队列满时,我们修改其判断条件,保留一个元素空闲,及队列满时保留一个空闲单元。为了使满队列和空队列区分,需要牺牲一个数组单元,提前定义满队列状态
假设有一个队列由N个元素,则顺序存储的队列需要建立一个大于N的数组
判满条件 (rear + 1)%N == front
front和rear向后走一步公式 rear = (rear+1)% N front = (front +1)% N
class SeqQueue:
def __init__(self, max_len=5):
self.max_len = max_len # 队列的最大长度
self.rear = 0 # 入队端
self.front = 0 # 出队端
self.data = [0] * max_len # 队列的存储空间
# 2.入列 data代表入列的数据
def enqueue(self, data):
if self.is_full(): # 判满
print("enqueue error")
return
self.data[self.rear] = data #队尾数据改为data
self.rear = (self.rear + 1) % self.max_len # 队尾指针rear向后走一步
# 3.判断队列是否为满
def is_full(self):
return (self.rear + 1) % self.max_len == self.front
# 4.判断队列是否为空
def is_empty(self):
return self.rear == self.front
# 5.出列
def dequeue(self):
if self.is_empty(): # 判空
print("dequeue error")
return
data = self.data[self.front] # 保存出列数据
self.front = (self.front + 1) % self.max_len front向后走一步
return data # 返回出列数据
# 6.求队列有效的长度
def length(self):
if self.rear >= self.front: # 队尾在队头后面时
return self.rear - self.front
else:
return self.rear - self.front + self.max_len # 队尾在队头前面时
# 7.清空队列函数
def clear(self):
self.rear = self.front = 0
if __name__ == '__main__':
s = SeqQueue()
s.enqueue(1)
s.enqueue(2)
s.enqueue(3)
s.enqueue(4)
print(s.dequeue())
print(s.dequeue())
print(s.length())
②链式栈
特点:只能头出尾进
逻辑结构:线性结构
物理结构:链式结构
有头单向链表,头结点为队头,终结点为队尾,对有头单向链表进行头删和尾插
入列需要建立新节点,让队尾指针指向对尾节点
出列有两种情况,普通情况,头结点跳过头节点的下一个节点,与下一个节点的下一个节点相连(即A.next=A.next.next)。当头结点后面只剩一个节点时,不能再跳过下一个节点。而且还需要考虑出列最后一个数据时,尾指针回到front位置,以便后续继续入列还需要用到尾指针rear。