该篇学习笔记来自于《你也能看得懂的python算法书》
“指针”是编程语言中的一个对象,它存储着一个内存空间的地址,计算机可以通过这个地址找到变量的值。也就是说,这个地址指向这个特定的值。指针最大的优点在于它可以有效利用零碎的内存空间
。通过列表
,可以建立内部只存在整形变量的数组,通过对一个数组中每个元素的下标来找出它的值,所以储存这个元素位置的下标值的变量
可以看作一个指针。我们将以这个概念来实现python中的指针问题,由于它不是真正意义上的指针,所以我们叫它“模拟指针问题”
。
一、数组合并
问题描述:
有两个从小到大有序排列的数组,第一个数组有5个元素,第二个数组里有4个元素,想要把它们合并成一个新的从小到大排列的数组,该怎么编程实现呢?
arr1=[1,3,4,6,10]
arr2=[2,5,8,11]
ind=0
ans=arr1.copy()
for i in range(0,len(arr2)):
while ind<len(arr1):
if arr2[i]<=arr1[ind]:
ans.insert(i+ind,arr2[i])
break
else:
ind+=1
else:#如果arr1已遍历完,直接把剩下的arr2拼接到arr1结尾
ans=ans+arr2[i:]
print(ans)
#输出:[1,2,3,4,5,6,8,10,11]
二、二分查找
问题描述:
如何快速在一个有序数组中准确地找到一个元素的位置呢?
解决思路:
二分查找又叫作折半查找,查找的范围都折半。这样查找到最后,查找范围内只剩一个数时,判断它是否为要查找的数。如果是,就记录它的位置;如果不是,则要查找的数不在这个数组中。
numbers=[1,2,3,4,5,6,8,13,14,17,18,24,30,43,56]
search=int(input("Enter a number to search:"))
head,tail=0,len(numbers)#数组值长度刚好是最大下标值+1
while tail-head>0:
mid=(tail+head)//2
if search>numbers[mid]:
head=mid+1
if search<numbers[mid]:
tail=mid
if search==numbers[mid]:
ans=mid
break
else:#当尾指针tail减头指针head等于1时,查找范围内只有head指向的数
if search==numbers[head]:
ans=head
else:
ans=-1
print(ans)
#输入:24 输出:11
if search==numbers[head]:为什么不用判断尾指针指向的数,而只用判断头指针指向的数呢?
在题目的开始可以看到,尾指针的定义是指向查找范围中最后一个数的后面的指针,它本身是不包含在查找范围内的。
三、链表
链表是用指针连接的用于存储数据的数组,它最大的优点在于可以有效地利用零碎的内存空间。而且使用链表,则可以改变数组的长度
,并且可以在同一个数组中存储不同类型的元素
。实际上,列表
的工作原理就是链表,在python中没有指针,所以用模拟指针的方法来实现链表。
1、建立单链表
什么是单链表?
链表的每个元素不仅仅存储这个元素的值
,还要储存与它相连的元素的指针的值
,这样链表才能被连起来。单链表的每个元素包含一个本身的值和一个指向下一个数的指针。因为链表的最后一个数没有下一个数,所以它的指针为空指针。
方法一:输出一个由两个列表组成的单链表
listvalue=[1,5,6,2,4,3]
listpointer=[3,2,-1,5,1,4]
head=0
print(listvalue[head])
next=listpointer[head]
while next!=-1:
print(listvalue[next])
next=listpointer[next]
方法二:输出一个列表套列表组成的单链表
value=0
pointer=1
linkedlist=[[1,3],[5,2],[6,-1],[2,5],[4,1],[3,4]]
head=0
print(linkedlist[head][value])
next=linkedlist[head][pointer]
while next!=-1:
print(linkedlist[next][value])
next=linkedlist[next][pointer]
执行结果如下:
2、建立双链表
和单链表类似,双链表中的每个元素是由它的值和两个指针组成的,一个指针指向下一个元素,一个指针指向上一个元素。双链表比起单链表的好处就是它可以双向遍历
,除了使用第一个元素到最后一个元素的顺序来遍历之外,还可以从最后一个元素到第一个元素进行遍历。无论遍历到第几个数,都可以通过指向前面元素的指针返回到前面
。
实现途径分两种,和单链表的两种建立方法一致
listvalue=[1,5,6,2,7,3]#第一种模拟双链表的方法
listright=[3,2,4,5,-1,1]
listleft=[-1,5,1,0,2,3]
head=listleft.index(-1)#头指针的值为-1在listleft中的位置
print(listvalue[head])
next=listright[head]
while next!=-1:
print(listvalue[next])
next=listright[next]
print()#换行隔开两个链表的输出结果
right=1#第二种模拟双链表的方法
left=2
value=0
linkedlist=[[1,3,-1],[5,2,5],[5,4,1],[2,5,0],[7,-1,2],[3,1,3]]
head=0#提前设置头指针指向的位置
print(linkedlist[head][value])
next=linkedlist[head][right]
while next!=-1:
print(linkedlist[next][value])
next=linkedlist[next][right]
执行结果如下:
3、双向输出双链表
双链表中的每个元素都有指向前后元素的两个指针,所以它可以被双向输出。
listvalue=[1,5,6,2,7,3]
listright=[3,2,4,5,-1,1]
listleft=[-1,5,1,0,2,3]
head=listleft.index(-1)
print(listvalue[head])
next=listright[head]
while next!=-1:
print(listvalue[next])
next=listright[next]
print()
head=listright.index(-1)#从这里开始是反向输出
print(listvalue[head])
next=listleft[head]
while next!=-1:
print(listvalue[next])
next=listleft[next]
执行结果如下:
4、向单链表中添加元素
1.为什么要把新元素的指针先指向它后面的元素,再让它前面的的元素指向它?
通过前一个元素的指针的存储位置,我们才能找到下一个元素的位置,从而和它连接上。如果在用新元素的指针存储下一个元素的位置之前,就把上一个元素的指针指向新元素,就丢失了下一个元素的位置。
2.由于列表中元素的排列顺序只取决于指针的顺序
,而与它实际在数组中的顺序
无关,所以直接把新元素的值加在储存的数组的末尾
即可。
def output(listvalue,listright,head):
print(listvalue[head])
next=listright[head]
while next!=-1:
print(listvalue[next])
next=listright[next]
listvalue=[1,5,6,2,7,3]
listright=[3,2,4,5,-1,1]
head=0
prepos=5#已知要插入的位置的上一个元素的位置
output(listvalue,listright,head)
print()
listvalue.append(4)#向数组末尾加上新元素的值4
listright.append(listright[prepos])#加上新元素指针指向的位置
listright[prepos]=len(listvalue)-1#上一个元素的指针指向新的元素
output(listvalue,listright,head)
执行结果如下:
5、向双链表中添加元素
1.给新元素指向前后元素的指针赋值,这两个指针的顺序可以调换
2.为前后两个元素指向新的元素的指针赋值
def output(listvalue,listright,head):
print(listvalue[head])
next=listright[head]
while next!=-1:
print(listvalue[next])
next=listright[next]
listvalue=[1,5,6,2,7,3]
listright=[3,2,4,5,-1,1]
listleft=[-1,5,1,0,2,3]
head=0
prepos=5#确定要插入的位置的第一个元素的位置
output(listvalue,listright,head)
print()
listvalue.append(4)
listright.append(listright[prepos])#给新元素的两个指针赋值
listleft.append(prepos)
listright[prepos]=len(listvalue)-1#将前后元素的指针指向新元素
listleft[listright[prepos]]=len(listvalue)-1
output(listvalue,listright,head)
执行结果如下:
双链表比单链表的好处就是可以通过知道上一个元素的位置来插入新元素,也可以通过知道下一个元素的位置来差插入新元素,大大方便了查找。单链表无法返回到上一个元素的特性给查找带来了很多麻烦,所以虽然双指针链表比单指针链表要复杂些,但它也更常用。
6、删除单链表中的元素
通过当前元素的指针来找到下下一个元素的位置,把这个位置赋给当前元素的指针,这样当前元素的指针就直接跳过下一个元素。
def output(listvalue,listright,head):
print(listvalue[head])
next=listright[head]
while next!=-1:
print(listvalue[next])
next=listright[next]
listvalue=[1,5,6,2,7,3]
listright=[3,2,4,5,-1,1]
head=0
prepos=5#确定要删除位置的前一个元素的位置
output(listvalue,listright,head)
print()
listright[prepos]=listright[listright[prepos]]
output(listvalue,listright,head)
执行结果如下:
7、删除双链表中的元素
1.把要删除的元素的前一个元素的right指针指向下下一个元素
2.把要删除的元素的后一个元素的left指针指向当前一个元素
def output(listvalue,listright,head):
print(listvalue[head])
next=listright[head]
while next!=-1:
print(listvalue[next])
next=listright[next]
listvalue=[1,5,6,2,7,3]
listright=[3,2,4,5,-1,1]
listleft=[-1,5,1,0,2,3]
head=0
prepos=5#确定前一个元素的位置
output(listvalue,listright,head)
print()
listright[prepos]=listright[listright[prepos]]#把前一个元素的right指针指向后一个元素
listleft[listright[listright[prepos]]]=prepos#把最后一个元素的left指针指向前一个元素
output(listvalue,listright,head)
执行结果如下: