内存
-
计算机的作用
- 存储和运算二进制的数据。
-
计算机如何实现1+1=?操作
- 将1加载到计算机内存中,然后基于计算机的加法寄存器对指定内存中存储的数据进行加法运算。
- 没有减法寄存器,+(-1)就是-1。
-
变量的概念
- 本质讲,变量指的就是计算机中的某一块内存空间。
- 内存空间有两个固有的属性
- 地址:使用16进制的数表示
- 作用:方便cpu寻址。门牌号。
- 大小:bit,byte,kb,mb,gb,tb
- 决定该块内存存储的数值的范围
- 地址:使用16进制的数表示
-
理解a=10的内存图(引用,指向)
- 引用:就是变量,通常讲,变量表示/存储的就是内存空间中某一块内存空间的地址。
- 指向:如果一个变量,引用存储/表示了某块内存空间的地址后,则该变量或引用指向了该块内存空间。
- eg:stu = Student(),stu指向了学生对象对应的内存空间
- next = Node(),next.item,next.next
-
给相同的数据类型的数据开辟固有大小的内存空间
- 整形数据:4byte
- 浮点型:4,8byte
- 字符型:1byte
个人理解
-
对于持久化存储来说,按照编码表存储:
ascii
:1word -> 1字节gbk
:1word -> 2字节utf-8
:1word -> 1-4字节(原ascii中所含的还是1个字节,欧洲2个字节,亚洲3个字节)
-
对于python内存空间来说:
-
根据每个变量赋值的数据类型不同,为变量预留的空间大小不同,决定预留内存空间大小的只有数据的数据类型,而数据本身的大小不会影响预留空间的大小
-
超过预留空间后会进行内存扩张,这样粗略的定制预留内存空间的大小虽然略微浪费资源,但是却节省了 cpu为每个不同大小的元素计算预留空间 的资源,而且如果根据数据大小计算内存空间,只要变量指向变了,又要重新计算预留空间,这就很占用cpu效率
-
python预留空间:
-
整形:4个字节;
-
1byte = 8bits
,1bit可以存储1位二进制数,那么一字节就可以存储8位二进制数,即可以存储2**8
(256)种十进制数字(0-255) -
同理,4个字节可以存储
2**32
种十进制数字np.iinfo('int8') # int,相较于无符号,需要浪费第一位用来储存+-号,2**7=128,所以范围就是 -127~127
iinfo(min=-128, max=127, dtype=int8)
In [6]:
np.iinfo('uint8') # 无符号int,2**8=256,范围就是0-255
iinfo(min=0, max=255, dtype=uint8)
-
-
浮点数:4/8个字节(单精度/双精度);
-
str:1个字符占1个字节,但是所有的字符串最后都有个
-0
,他还要额外多用一个字节(例如,'hello' --> 6个字节)
-
-
顺序表
分类
-
数据结构中存储的元素是有顺序的,顺序表的结构可以分为两种形式:
- 单数据类型(数组)和多数据类型(列表)。
-
python中的列表和元组就属于多数据类型的顺序表
-
单数据类型(数组)顺序表的内存图(内存连续开辟)
- 数组种数据类型是统一的,所以偏移量是固定的
- 这就可以解释索引的来历了
-
多数据类型顺序表的内存图(内存非连续开辟)
- list中可以存储不同的数据类型,所以偏移量不同,我们可以通过存储内存地址的形式来实现list的索引(相同偏移量)
上图个人理解:
-
顺序表分类:
- 单数据存储结构、多数据存储结构
-
数组:单数据存储结构
- 数组中存储的都是元素本身
- 数组内数据类型必须统一:为了方便偏移量计算
-
列表:多数据存储结构
- 列表中存储的都是内存地址!!!
- 列表中各元素都不是直接存储在列表中的,他们都是以 非连续存储 的形式存储在内存空间中的,而列表指向的仅仅是个 存储着各元素内存地址 的有序容器,也是因为这个原因,所以list中可以存储不同数据类型的数据,但是索引依然不变
-
顺序表缺点:插入数据时,插入位置以后的所有元素的索引都要变,时间复杂度高
-
链表缺点:必须要一个一个找,查询效率低
弊端:
- 顺序表的结构需要预先知道数据大小来申请连续的存储空间
- 何为连续的存储空间?
- 就是说使用内存时,不是按照顺序一个个往下使用,而是看谁有空就存在哪里,这样就会产生马蜂窝那样的情况,很多空间被零碎的使用,所以想要找一个连续、且足够大的内存空间或许会耽误效率
- 何为连续的存储空间?
- 而且在进行内存空间扩充时又需要进行数据的搬迁,又要查找满足条件的空间
链表
- 链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是不像顺序表一样连续存储数据,而是每一个结点(数据存储单元)里存放下一个结点的信息(即地址)
优点:
- 相对于顺序表,链表的每一个节点都可以不连续,所以可以充分利用计算机内存空间,只要通过每一个节点中存储的下一个节点的内存地址就可以实现链式查找,所以不需要连续存储。这样就实现了灵活的内存动态管理
- 进行扩充时不需要进行数据搬迁,只要在最后一个节点中存储上新节点的内存地址即可
链表需要实现的方法:
- .is_empty():链表是否为空
- .length():链表长度
- .travel():遍历整个链表
- .add(item):链表头部添加元素
- .append(item):链表尾部添加元素
- .insert(pos, item):指定位置添加元素
- .remove(item):删除节点
- .search(item):查找节点是否存在
节点
# 节点的封装
class Node(object):
def __init__(self,item):
self.item = item
self.next = None
链表
class Link(object):
"""可以发现链表的各种方法中如果涉及第一个节点都要额外定义,因为第一个节点连接的是self._head"""
def __init__(self):
self._head = None
def is_empty(self):
return self._head == None
def add(self,item):
"""三步走,实例化节点,新节点连上一个节点,self._head连新节点"""
node = Node(item)
node.next = self._head
self._head = node # 将_head指向当前节点
def append(self,item):
node = Node(item)
# 如果链表为空
if not self._head:
self._head = node
return
cur = self._head
pre = None # 用来存储cur的上一个节点,因为while循环最后会找到None为止,到时候再找上一个对象就麻烦了,所以我们手动存储
while cur:
pre = cur
cur = cur.next
pre.next = node # 当while循环结束的时候,pre就指向了链表中最后一个节点,而不是None
def travel(self):
# cur指向了第一个节点
# _head要永远指向第一个节点,轻易不要修改_head的指向,不然可能会导致数据丢失,
# 因为你每个节点就只存储了下一个点的内存地址,一个点断了,后面的数据就都找不到了
cur = self._head
while cur:
print(cur.item)
cur = cur.next
def length(self):
count = 0
cur = self._head
while cur:
count += 1
cur = cur.next
return count
def search(self,item):
"""查找item对应的节点是否存在,返回Bool"""
cur = self._head
while cur:
if cur.item == item:
return True
cur = cur.next
return False
def insert(self,pos,item):
"""
插入操作核心在于pre跟cur代表两个连续的节点,根据索引进行循环得到pre跟cur后,
pre.next连接node,node.next连接cur
"""
node = Node(item)
if pos == 0: # 插入首位需要调整self._head值
node.next = self._head
self._head = node
return
# 在非首位插入元素
cur = self._head
pre = None
for i in range(pos):
pre = cur
cur = cur.next
pre.next = node
node.next = cur
def remove(self,item):
"""删除核心在于,并没有进行删除操作,只是将要删除节点的前后节点直接进行连接,跳过该节点"""
if self._head.item == item:
self._head = self._head.next
pre = None
cur = self._head
while cur.next: # 这里为了避免循环内cur.next得到的是None,下面的cur.item会报错,'NoneType' object has no attribute 'item'
pre = cur
cur = cur.next
if cur.item == item:
pre.next = cur.next
return
使用:
link = Link()
#link.add(1)
#link.add(3)
#link.add(2) # 2,3,1
link.append(4)
link.append(6)
link.append(5) # 2,3,1,4,6,5
link.travel()
print(link.search(1))
4
6
5
False
insert
link = Link()
link.append(4)
link.append(5)
link.append(6)
link.insert(0,7) # 插哪个位置都可以
link.travel()
7
4
5
6
remove
link = Link()
link.append(4)
link.append(5)
link.append(6)
link.remove(5) # 插哪个位置都可以
link.travel()
4
6
面试题:
- 自行实现链表翻转
- 1,2,3,4,5
- 翻转后编列:5,4,3,2,1
- 要求:修改节点的指向实现链表的倒置输出!
代码:
# 节点的封装
class Node(object):
def __init__(self,item):
self.item = item
self.next = None
class Link(object):
def __init__(self):
self._head = None
def is_empty(self):
return self._head == None
def add(self,item):
"""三步走,实例化节点,新节点连上一个节点,self._head连新节点"""
node = Node(item)
node.next = self._head
self._head = node # 将_head指向当前节点
def append(self,item):
node = Node(item)
# 如果链表为空
if not self._head:
self._head = node
return
cur = self._head
pre = None # 用来存储cur的上一个节点,因为while循环最后会找到None为止,到时候再找上一个对象就麻烦了,所以我们手动存储
while cur:
pre = cur
cur = cur.next
pre.next = node # 当while循环结束的时候,pre就指向了链表中最后一个节点,而不是None
def travel(self):
# cur指向了第一个节点
# _head要永远指向第一个节点,轻易不要修改_head的指向,不然可能会导致数据丢失,
# 因为你每个节点就只存储了下一个点的内存地址,一个点断了,后面的数据就都找不到了
cur = self._head
while cur:
print(cur.item)
cur = cur.next
def length(self):
count = 0
cur = self._head
while cur:
count += 1
cur = cur.next
return count
def search(self,item):
"""查找item对应的节点是否存在,返回Bool"""
cur = self._head
while cur:
if cur.item == item:
return True
cur = cur.next
return False
def insert(self,pos,item):
"""
插入操作核心在于pre跟cur代表两个连续的节点,根据索引进行循环得到pre跟cur后,
pre.next连接node,node.next连接cur
"""
node = Node(item)
if pos == 0: # 插入首位需要调整self._head值
node.next = self._head
self._head = node
return
# 在非首位插入元素
cur = self._head
pre = None
for i in range(pos):
pre = cur
cur = cur.next
pre.next = node
node.next = cur
def remove(self,item):
"""删除核心在于,并没有进行删除操作,只是将要删除节点的前后节点直接进行连接,跳过该节点"""
if self._head.item == item:
self._head = self._head.next
pre = None
cur = self._head
while cur.next: # 这里为了避免循环内cur.next得到的是None,下面的cur.item会报错,'NoneType' object has no attribute 'item'
pre = cur
cur = cur.next
if cur.item == item:
pre.next = cur.next
return
def reverse(self):
"""实现链表的反转"""
cur = self._head
pre = None
while cur:
storage = cur.next # 先把下一个节点存起来,不然数据周转不开
cur.next = pre # 下一个节点的next指向上一个节点
pre = cur # 老操作了
cur = storage # cur往前推进
self._head = pre
使用:
li = Link()
items = [1,2,3,4,5]
for item in items:
li.append(item)
li.travel()
li.reverse()
print('-')
li.travel()
结果:
1
2
3
4
5
-
5
4
3
2
1