数据结构与算法(线性表、栈、队列)
*最近在学习数据结构与算法,期间也由于理解的不透彻死活不能用程序去实现它。所以我将我的学习过程以及期间出现的问题整理出来,这也是我的再学习过程,也希望能对大家有一点帮助。栈和队列都是使用顺序表实现的,因为相对来说比较简单;在后续的树和图中,使用链表相对简单,所以在树和图中我在放出链表的代码。
逻辑关系
· 在数据结构中,根据逻辑关系可大致分为线性结构(线性表、栈、队列、字符串、数组、广义表)以及非线性结构(树、图)。其中,最为简单且基础的是线性结构,在之后的学习过程中,任何类型的数据结构都或多或少要以它们为基础去创建,去构造。所以在学习的开始,我先基于我的一些不成熟的理解去使用python完成对线性表、栈、队列的构造。
线性表:
1.描述: 线性表是由n (n>=0)个类型相同的数据元素(结点)a1,a2,….,ai, ….,an组成的有限序列。其中,数据元素的个数n定义为表长。当n=0时称为空表,非空的线性表(n>0) 记为:(a1,a2,….,ai,……,an)
2.逻辑特征:仅有一个开始结点和一个终端结点,并且所有结点都最多只有一个直接前趋和一个直接后继。
3.线性表的特点:
同一性:线性表由同类数据元素组成,每一个ai必须属于同一数据对象。
有穷性:线性表由有限个数据元素组成,表长度就是表中数据元素的个数。
有序性:线性表中相邻数据元素之间存在着序偶关系<ai,ai+1>。
4.线性表的抽象数据类型定义:
ADT LinearList{
数据元素:D={ai| ai∈D0, i=1,2,…,n,n≥0 , D0为某一数据对象}
关系: S={<ai,ai+1> | ai, ai+1∈D0, i=1,2, …,n-1 }
基本操作:
(1)InitList(L)
(2)DestroyList(L)
(3)ClearList(L)
………
}ADT LinearList
5.线性表的常见基本运算包括:
(1).初始化空表InitList(L)
(2).清空列表ClearList(L)
(3).销毁表Destroylist(L)
(4).求表长ListLength(L)
(5).判断是否为空表EmptyList(L)
(6).定位Locate(L,e)
(7).插入InsList(L,i,e)
(8).删除DelList(L,i,e)
顺序表
1、顺序存储:将线性表的元素按逻辑次序依次存放在一组地址连续的存储单元里。使得线性表中在逻辑结构上相邻的数据元素存储在相邻的物理存储单元中。
2、顺序表:采用顺序存储方法存储的线性表称顺序表。
3、存储地址的计算:
*因为python中不存在数组,所以我们要借助列表去实现数组。python中列表的方法已经非常完善了。例如顺序表中按位置查找元素,按元素查找位置,列表的index方法就已经实现了。所以在构建顺序表时,我们只借助列表,利用列表的索引充当指针去完成对顺序表的构建。
需要注意的问题:
1.顺序表的大小:数据结构中,顺序表是一个具有固定长度的结构;但在python中,列表是根据数据自动增加空间,不能直接给死长度。所以,在初始化顺序表时,我们用列表的构造方法并使用一个符号去占位(符号的个数即为顺序表的最大长度)。
2.顺序表的增加删除操作:在顺序表中,增加删除操作都类似于覆盖操作(增加时,从末尾至给定位置的元素统统后移覆盖下一元素;删除时,从给定位置至末尾元素统统前移覆盖上一元素)。我们在构建方法时,即用i去遍历列表,使得[i]与[i+1],[i-1]的值不断替换。
#创建顺序表
class sqplist():
def __init__(self,max):
self.max = max
#查询表是否满
def no_empty(self):
if len(num) == self.max - 1:
print('顺序表已充满,请勿直接添加')
def empty(self):
if len(num) == 0:
print('顺序表为空')
#顺序表的添加方法
def add(self,x,m):
for i in range(len(num)-1,m-1,-1):
num[i] = num[i-1]
# print(num[i])
num[m-1] = x
#顺序表的删除方法
def remove(self,y):
if y in num:
n = num.index(y)
for i in range(n,len(num)-1):
num[i] = num[i+1]
else:
print('%s不存在顺序表内'%y)
#打印顺序表
def print(self):
print(num)
if __name__ == '__main__':
max = int(input('请输入您定义顺序表的长度'))
num = [''] * max
sqp = sqplist(max)
while True:
choose = int(input('请输入您要进行的操作:1.增加 2.删除 3.打印 4.退出'))
if choose==1:
x = int(input('请输入您要添加的值'))
m = int(input('请输入您要添加的位置'))
sqp.add(x,m)
elif choose==2:
y = int(input('请输入您要删除的值'))
sqp.remove(y)
elif choose==3:
sqp.print()
elif choose==4:
break
栈、队列
什么是栈和队列呢?
简单而言,你和你的朋友们依次通过一个狭小的胡同,突然发现它是一个死胡同,大家要折返回来,那么最先进去的就是最后出来的,我们只能从后往前输出,这就是栈。栈遵循先进后出的思想,它拥有栈顶指针并且永远指向栈顶。
队列,是一种先进先出的结构体。类似于大家排队进场,先排队的先出,后排队的只能等前面的人进入之后才能进入,按照逻辑顺序上的从前往后输出,这就是队列。队列包含队首和队尾两个指针,一个永远指向队首,一个永远指向队尾。
因为python中没有指针,所以我们在方法类中,初始化属性去代替完成指针的操作,去模拟指针完成增加删除操作。
栈
1.定义:栈(Stack)是仅在表的一端进行插入和删除运算的 线性表
栈顶(top)为进行插入和删除运算的一端
栈底 (bottom)为另一端
2.逻辑结构:与线性表相同,仍为一对一(1:1)关系。
3.存储结构:用顺序栈或链栈存储均可,但以顺序栈更常见。
4.特点: 最先入栈的元素总是最后出栈,而最后入栈的元素则总是最先出栈,因此,栈又被称为后进先出(Last In First Out)的线性表。
5.栈的基本操作:
1.InitStack(S) 2. ClearStack(S)
3. IsEmpty(S) 4. IsFull(S)
5. Push(S,x) 6. Pop(S,x)
7. GetTop(S,x)
6.顺序栈存储结构描述:
#define Stack_Size 50
typedef struct
{ StackElementType elem[Stack_Size];
int top;
} SeqStack;
SeqStack *s;
#创建方法类
class inn():
#初始话属性
def __init__(self,size):
self.size = size
self.list = ['']*self.size
#代替栈顶指针
self.top = 0
self.end = 0
#判断栈是否为=已满
def if_empty(self):
if self.top == self.end:
print('栈为空')
else:
print('栈不为空')
#栈的插入操作
def insert(self,num,loc):
if loc< self.size and loc-1>self.end:
#利用索引完成插入
self.list[self.end] = num
self.end +=1
else:
print('栈满,无法入栈')
#定义出栈操作
def pop(self):
#弹出栈顶元素,即列表最后一个元素
self.list[self.end-1] = ''
self.end -=1
#打印方法
def show(self):
a = []
for i in self.list:
if i == '':
break
else:
a.append(i)
print(a)
print(self.end)
if __name__ == '__main__':
inn = inn(10)
inn.insert(8,2)
inn.insert(51,4)
inn.insert(81, 4)
inn.pop()
inn.insert(10,7)
inn.show()
inn.if_empty()
队列
1定义:队列(queue) 是一端进行删除另一端进行插入的线性表。 允许插入的一端称为队尾(rear) ,允许删除的一端称为队头(front)。
2特点: 先入队的元素必将被先出队。因此,队列是一种先进先出(First In First Out)的线性表。
*在这里,我们用最简单的顺序表去实现队列。在实现的过程中,我们需要注意几个问题。
我们在使用顺序表实现队列时,因为顺序表的定义中必须给定最大长度,所以就会出现一个问题:(1).当第一次添加队列时,我们从首地址添加,此时队首指针指向首地址。但是由于队列先进先出的性质,当我们输出元素时,会先从首地址输出。(2).此时首地址的值被输出,队首指针后移。而队列的添加是从队列末尾添加的,此时就会出现空间浪费的情况。(3).假设队列重复(1)(2)两个步骤,那么会出现虽然有空间,但是队列却显示已满,这就是空间浪费的问题。
*解决方式:
我这里给大家提供几个思路:
(1).第一个,也是大家使用最多的方法,就是使用循环列表,当队尾指针已经指向列表的最大长度且队首指针前还有空间;此时插入元素就从首地址开始插入,队首指针跟随插入元素移至首地址。即将顺序表构成一个环,就可以避免空间浪费的情况了。
使用循环队列创建列表,我们需要解决几个问题:
①.如何判断队列为空还是满?
方法一:引入标志tag
初始化时,q->rear=q->front=0,tag=0;
当元素进队时,tag=1;当元素出队时,tag=0;
若 (q ->front=q ->rear)&&(tag=0),则队空,不能出栈。
若 (q ->front=q ->rear)&&(tag=1),则队满,不能入栈。
②.我们如何去让指针循环指向队列呢?
采用循环队列后,进行入队和出队运算时,头、尾指针加1操作应如下进行:
出队: q ->front=(q ->front+1)% maxsize;
入队: q ->rear=(q ->rear+1)% maxsize;
(2).我们在弹出元素时,我们将弹出的空间补至队列后,即我们可以将索引减去弹出元素的个数(假设弹出一个元素,此时队首指针后移至1,我们在使用索引时减去弹出个数,此时指针其实还是指向首地址的;队尾指针亦是如此)。这个方法是通过不改变索引值去造成一种指针后移的假象。这种方法类似于一种作弊的方式,虽然给人一种队列被循环使用的假象,但实则队首指针并未改变过。
#创建队列方法类
class queue():
#初始话队列,此时传入队列最大长度
def __init__(self,max):
self.max = max
#初始化队列,使用占位符占位
self.list = ['']*self.max
self.front = 0
self.rear = 0
#设置一个监控属性
self.stautes = 0
#判断队列是否为空
def if_empty(self):
if (self.rear+1)%self.max == self.front:
self.stautes = 2
print('队列已满')
elif self.rear == self.front:
self.stautes = 0
print('队列为空')
else:
self.stautes = 1
print('队列未满')
#队列的插入方法
def insert(self,num):
if self.stautes == 0:
self.list[self.rear] = num
self.rear = (self.rear + 1)%self.max
else:
print('队列已满')
#打印队列
def show(self):
print(self.list)
if __name__ == '__main__':
s = queue(20)
s.insert(20)
s.if_empty()
s.show()
这是我在数据结构的基础学习时遇到的问题,可能更多的仅仅将我的思路展示了出来,并没有很好的实现,希望能对大家有一点点帮助,也谅解因为初学导致的思想或代码的幼稚。