数据结构
链表
链表是什么
链表是由一系列的节点构成(链表中的每个元素都称为节点),它是一种递归的数据结构。它能在数据之间保持逻辑顺序,但是在存储空间中不必按照顺序存储,使用的可以不是连续的内存。在链表中不存在索引。在Python中以面向对象(class)的形式进行创建等操作。
Python中的链表
链表中的基本元素是节点,每个节点分成两部分一个是存储数据元素的数据域,另一个是存储下一个节点地址的指针域。结构如下
创建一个链表
第一种方法:
1.创建一个节点类:
class Node:
def __init__(self,data):
slef.data = data
self.next = next
if __name__ == '__main__':#主函数的程序入口
node1 = Node(1)
node2 = Node(2)
node3 = Node(3)
node4 = Node(4)
node5 = Node(5)
node6 = Node(6)
node7 = Node(7)
node1.next = node2
node2.next = node3
node3.next = node4
node4.next = node5
node5.next = node6
node6.next = node7
node7.next = None # 最后一个指向None
#输出方法
curr = node1
while curr.next:
print(curr.data)
curr = curr.next
第二种方法
1.创建节点类
#节点类
class Node:
def __init__(self,data,next = None):
self.data = data #数据域:在这里为一个节点赋值
self.next = None #指针域:在这里链接下一个节点的存储位置,刚定义的情况下设置为None
#对reper进行重写,返回我们需要的形式。
#以字符串形式输出
def __reper__(self): #reper转化为解释器读取的形式
return "Node({}).format(self.data)"
2.创建一个链表类
#创建一个链表类
class LinkList:
#初始化一个链表的头部
def __init__(self):
self.head = None #此时并没有赋值所以设置为None
#插入!!!
-----------------------#从链表头部插入值------------------------
#思路:如果链表中有值就向链表头部左侧插入值,如果没有值就创建一个头部.
#在每次插入值得时候都会创建一个新的节点
def insert_data(self,data):
new_node = Node(data) #这里需要调用节点类将节点类中的值传递给new_node
if self.head: #如果存在head 这里完整形式为 : self.head is Not Null
new_node.next = self.head #将原来的head的值放在 new_node的后面
self.head = new_node # 如果不存在就把值给到head
-----------------------#从链表的尾部插入值-----------------------
#思路:从头部开始,遍历到最后一个节点的值,然后在给这个节点添加一个指针
def append_data(self,data)
tempNum = self.head
if self.head: #依旧是判断head 是否存在
# 下面的while判断tempNump 是否为空,如果为空就跳出循环.省略了tempNum.next is not Null
temp = self.head
while tempNum.next:
tempNum = tempNum.next
tempNum.next = Node(data) #每次赋值都需要从Node中给到
else:#当没有头部的时候就相当于在左侧添加值
self.insert_data(data)
------------------------#从链表的中间插入值---------------------------
#思路: 设置两个变量起头并进的循环遍历,然后当第一个变量(temp)到达指定位置的时候,再在第一个变量(temp)和第二个变量(pre)之间进行插入
def insert(self,i,data):
#当没有头部或者在第一个位置插入的时候直接创建一个新的节点
if self.head is None:
#下面直接为头部赋值,不需要再用node给到值,在insert_data 中已经有了Node
self.insert_head(data)
elif i == 1:
self.insert_head(data)
else:
temp = self.head
j = 1
pre = temp
while j<i: #再i的前面插入新的值所以要j<i
pre = temp
temp = temp.next
j +=1
node = Node(data)
#再上一步执行结束之后,node的值存在了但是并没有与pre链接起来,下一步就是pre与node之间建立连接
pre.next = node
#node 插入到了pre 与temp 之间上一步建立了pre与node的链接,下一步就需要建立node与temp之间的链接.
node.next = temp
#转化!!!
--------------------------- #列表转为链表---------------------------------
#思路 :遍历列表中的值 逐一赋给链表
def List_to_Link(self,object:list)#object在这里是类型注解
self.head = node(list[0])
temp = self.head
for i in list[1:]:
Node = node(i)
temp.next = Node
temp = temp.next
------------------------------#输出-------------------------------
def __repr__(self):
cursor = self.head
result = ""
while cursor:
result += f"{cursor} -->"
cursor = cursor.next
return result + "end"
#删除!!!
#删除头结点
#思路:让头节点连接到下一个节点然后再将下一个节点命名为头结点,进行结束后,原来的头结点并没有被删除,只是被分理出了整个链表。
def delete_head(self):
temp = self.head
if self.head:
self.head = self.head.next
temp.next = None #将self.head指向下一个断开
#删除尾部节点
def delete_taile(self):
temp = self.head
#判断链表是否为空,或者只有一个
if self.head:
if self.head.next is None:
self.head = None
else:
while temp.next.next:
temp = temp.next
temp.next= None
#调用
link = linkedlist()
link.insert_data(1)
link.insert_data(2)
link.appent_data(3)
link.insert(2, 9)
a = linkedlist()
a.list_to_link(list(range(3)))
a.delete_head()
print(a)
删除,插入的整合
总体思路: 在上面的基础中我们将插入删除等操作进行了分块,在头部插入,在中间插入,在尾部插入等等…这样的方法过于累赘,我们可以在插入或者删除的时候添加一个位置属性,来将三种函数整合为一个函数.
#首先建立一个节点类
class Node:
def __init__(self,data):
self.data =data
self.next = None
#设置一个可视化的输出
def __repr__(self):
return f"Node({self.data})"
#设置一个链表类
class LinkList:
#给链表设置头部,尾部和链表的长度
def __init__(self,data):
self.head = None
self.tail = None
self.size = 0
#因为要获取到每插入的位置,所以设置可以获取到节点位置的函数
def get(self,index): #
#首先需要查找到链表头
curr = self.head
#已知次数的时候更适合用for循环
for _ in range(index):
curr = curr.next
return curr
#下面是插入函数
def insert(self,index,data) #两个属性,位置以及这个位置的值
new_node = Node(data) #进行赋值方便后面的程序
#首先判断 是否越界,如果越界就进行报错
if index < 0 or index > self.size
raise.Exception("索引越界")
else:#下面进行插入操作,同样分情况,同事根据输入的index 的值进行分类
#第一种情况:当链表为空的时候
if self.size == 0 :
self.head = new_node
self.tail = new_node
#第二种情况:在头部插入,在头部插入的时候就是index = 0 的时候
elif index == 0:
new_node.next = self.head #另new_node 指向 原来的self.head
self.head = new_node #重新定义self.head 头部的值
#第三种情况:在尾部插入,index = self.size,思路与第二种情况相同
elif index == self.size:
self.tail.next = new_node
self.tail = new_node
#第四种情况:在中间插入
#思路:举例:我们向第5个位置插入一个新的节点 ,那么我们需要获取到地四个位置的节点另第四个位置的节点链接
else:
prev = self.get(index-1) #获取到index - 1 位置的节点
new_node.next = prev.next #首相将new_node指向prev.next 是prev对prev.next的指向并没有断开
prev.next = new_node #再将prev指向域指向new_node
self.size +=1 #在插入一个节点之后需要令节点数加一
def remove(self, index):
if index <0 or index >= self.size:
raise Exception('索引越界')
#删除头部
if index == 0:
remove_node = self.head
self.head = self.head.next
remove_node.next = None
#删除尾部
elif index == self.size - 1 :
prev = self.get(index -1)
remove_node = prev.next
prev.next = None
self.tail = prev
else:
prev = self.get(index -1 )
remove_node = prev.next
prev.next = prev.next.next
self.size -=1
return remove_node
#输出链表
def __repr__(self):
cursor = self.head
result = ""
while cursor:
result += f"{cursor} -> "
cursor = cursor.next
return result + "end"
注意点:
边界情况
遍历
头部
temp.next = new_node 前一个节点指向后一个节点
临时变量或者辅助变量,帮助记忆 例如: temp 和 pre 就是帮助你记忆节点的位置
链表中的环
环的概念:
当一个单链表存在循环的时候就是存在一个环。在这里我们主要学习了:判断是否存在环、寻找入环点和相遇点。
判断是否是环
原理:使用快慢指针法,两者从同一侧出发,快指针一次走两步,慢指针一次走一步,如此进行的话两个指针必然会陷入链表的环中,并且必然会有机会重叠。所以我们利用这个原理,当快慢指针发生重叠的时候,就是存在环。在这里用到了一种常用的方法:快慢指针(点击跳转)
# 定义一个类属性
class Node:
def __init__(self, data):
self.value = data
self.next = None
#函数用来判断是否存在环
def is_circle(head: Node): # 类型注解,没有实际意义 ————> 与这个类似,
fast = head # 两者都需要从头部出发,
slow = head
while fast and fast.next:
fast = fast.next.next # 快指针一次走两步
slow = slow.next # 慢指针一次走一步
if fast == slow:
return True
return False # 如果不存在环就会因为fast或者fast.next为空跳出循环
if __name__ == '__main__': # 主函数程序的入口
node1 = Node(1)
node2 = Node(2)
node3 = Node(3)
node4 = Node(4)
node5 = Node(5)
node6 = Node(6)
node7 = Node(7)
node1.next = node2
node2.next = node3
node3.next = node4
node4.next = node5
node5.next = node6
node6.next = node7
node7.next = node4
print(is_circle(node1))
判断入环点和相遇点
相遇点:当快慢指针陷入环中的时候,必然会有重叠的时候,发生重叠现象的点就是相遇点
入环点:顾名思义,快指针和慢指针相遇的点机试入环点。快指针从相遇点出发,慢指针从头部出发,两者再次相遇的节点就是入环点。
其中存在一些数学问题想要了解详情:请按住Ctrl左键点击这里跳转链接
# 创建一个节点
class Node:
def __init__(self, data):
self.data = data
self.next = None
def __repr__(self): # 用于输出节点中的内容
return f"Node({self.data})"
def detectCirclePoint(head): # head是节点的头部
# 找到相遇点
fast = head
slow = head
#
while fast and fast.next:
fast = fast.next.next
slow = slow.next
if fast == slow:
break
# 找到入环点
slow = head
while slow != fast:
slow = slow.next
fast = fast.next
return slow
if __name__ == '__main__': # 主函数程序的入口
node1 = Node(1)
node2 = Node(2)
node3 = Node(3)
node4 = Node(4)
node5 = Node(5)
node6 = Node(6)
node7 = Node(7)
node1.next = node2
node2.next = node3
node3.next = node4
node4.next = node5
node5.next = node6
node6.next = node7
node7.next = node2
print(detectCirclePoint(node1))
栈
概念:
栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。先入后出
栈的图解:
python中的栈
1、用列表实现栈
思想就是利用列表的方法,例如向列表中添加元素:append 从列表中删除元素:pop.因为栈遵循先入后出,先入的应该在最下层的原则,所以使用这两个列表方法.
class Stack:
#创建一个空的栈
def __init__(self):
self.stack = []
self.size = 0
def __str__(self):
return str(self.stack)
#压栈,向栈中添加元素,向里面添加的第一个元素会在栈的最下面,最后一个添加的元素会在栈的最上面.
def push(self,data):
self.stack.append(data)
self.size +=1 #在每次添加之后要让栈的大小加一
#弹栈,从栈中移除元素,遵循先进后出的原则
def push(self,data):
#首先判断栈中是否有元素,如果有就移除,如果没有就报错
if self.stuck:
temp = self.stack.pop() #pop默认移除列表的最后一个元素
self.size -= 1
return temp #这里返回了你所移除的元素
else:
raise IndexError("pop from an empty stack")
#返回栈顶 ,栈顶说的是栈的最上层的元素,在列表中就是最后一个元素
def peep(self):
if self.stack:
return self.stack[-1]
#判断栈是否为空
def is_empty(self):
#bool 判断的是 列表中是否有元素,所以要判断列表是否为空 就需要加上 not
return not bool(self.stack)
#返回栈的大小·
def sizeaa(self) #为了区分方法和属性,就为方法更改了方法名
return self.size
2、用链表创建栈
这里只考虑怎样向栈中添加和删除元素,向栈中添加元素就是在原有的最后一个基础上,指向新的节点.删除元素就是另倒数第二个节点指向空
#首先创建一个节点类
class Node:
def __init__(self,data):
self.data = data
self.next = None
#用链表创建栈
class LinkStack:
def __init__(self):
self.top = None #规定栈顶,便于在后期添加元素
self.size = 0
def push(self,data):
node = Node(data)
if self.top is None: #判断栈是否为空 如果为空就为栈顶添加新的值
self.top = node
else:
#顶部指向底部
node.next = self.top # 令新的节点指向top
self.top = node #更换栈顶
self.size +=1
def pop(self):
if self.top is None: #如果栈为的话就报错
raise IndexError("pop from empty stack")
else:
###有疑问
node = self.top
self.top = node.next
node.next = None
self.size -=1
return node
队列
队列的概念:
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。与栈相反的是,队列遵循的是先入先出的原则。
1.用列表实现队列
class Queue:
def __init__(self):
self.entries = []
self.size = 0
def __repr__(self): # 在程序执行结束后,直接输出调用对象的变量就会显示内容
printed = "<" + str(self.entries)[1: -1] + ">"
"""
因为str在转换这个属性的时候会把列表两个[] 也转化为字符串,所以要设置[1:-1]
"""
return printed
# 入队
def enQueue(self,data):
self.entries.append(data) # 方法简单粗暴
self.size += 1 # 改变队列的大小
#出队
def deQueue(self,data):
temp = self.entries.pop(0) # 因为先入先出,所以要pop(0)
self.entries = self.entries[0:]
rerurn temp
a = Queue()
a.enqueue(1)
a.enqueue(2)
a.enqueue(3)
a.enqueue(4)
print(a)
a.dequeue()
print(a)
2.用链表实现队列
class Node:# 创建节点
def __init__(self,data):
self.data = data
self.next = None
class LinkQueue:
def __init__(self):
self.front = None # 设置一个头部的属性
self.rear = None # 设置一个尾部的属性
self.size = 0
def is_empty(self): # 判断队列是否为空
return self.fronr is None
def put(self,item):
node = Node(item)
if self.is_empty(): # 如果队列为空的话,就直接赋值
self.front = node
self.rear = node
self.size += 1
else:
self.rear.next = node # 在尾部链接洗的元素
self.rear = node # 重新命名尾部节点
self.size += 1
def DeQueue(self):
if is_empty:
raise IndexError("错误提示:您操作的队列元素为空!!")
else:
node = self.front # 把删除的的值给到node
self.front = self.front.next # 节点后移
return node.data
def __repr__(self):
curr = self.front
str_d = ""
while curr:
str_d += f"{curr.data}-->"
curr = curr.next
return str_d
if __name__ == '__main__':
a = LinkQueue()
a.put(1)
a.put(2)
a.put(3)
a.put(4)
a.put(5)
a.put(6)
a.put(7)
print(a)
print(a.DeQueue())
print(a)
树
概念与特点:
概念:树是一种数据结构,它是由N个有限节点组成的一种具有层次关系的集合,把它叫做树是因为它看起来像一颗倒挂的树,根在最上方,而叶子在根的下方。
左子树:根节点左侧的树
右子树:根节点右侧的树
特点:每个结点有零个或多个子结点;没有父结点的结点称为根结点;每一个非根结点有且只有一个父结点;除了根结点外,每个子结点可以分为多个不相交的子树;
树的分类
二叉树:每个节点最多含有两个子树的树称为二叉树;
满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。
完全二叉树:一棵深度为k的有n个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为i(1≤i≤n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树
满二叉树和完全二叉树的区别:
1、满二叉树:从形象上来说满二叉树是一个绝对的三角形,也就是说它的最后一层全部是叶子节点,其余各层全部是非叶子节点,
2、完全二叉树:完全二叉树的节点个数是任意的,从形式上来说他是一个可能有缺失的三角形,但所缺部分肯定是右下角的某个连续部分。这样说不玩整,更准确来说,我们可以说他和满二叉树的区别是,他的最后一行可能不是完整的,但绝对是右方的连续部分缺失。
二叉搜索树:若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。
二叉搜索树
在这里我们主要讨论的是二叉搜索树:每个节点最多含有两个孩子的树左小于根,右大于根。
插入和查找
思路:
对于插入:
根据二叉搜索树的特点:左侧小于根,右侧大于根。我们在进行插入的时候需要进行多次判断
判断一: 判断根是否存在,如果存在的话就进行大小的比较,如果不存在就将新的节点值赋予根节点
判断二:与父节点相比判断大小,如果小于就放在左侧,如果大于就放在右侧
判断三:判断父节点的左节点是否为空如果为空就可以直接进行插入操作,如果不为空就需要进行父节点下移的操作并且再次进行判断二和判断三
对于查找:
同样需要根据二叉搜索树的特点来进行操作:
判断一:判断根节点是否存在
判断二:如果根节点存在,判断根节点是否是我们要查找的值,如果不是就节点下移
判断三:在结点下移的时候需要进行判断大小的操作,如果大于根节点就向右侧移动,如果小于根节点就向左侧移动
from pprint import pformat # 一种输出形式
class Node:
# 从树的结构可已看出
def __init__(self, value, parent):
self.value = value
self.left = None
self.right = None
self.parent = parent
def __repr__(self):
if self.left is None and self.right is None:
return str(self.value)
# 需要注意下这种特殊的输出方式
return pformat({"%s" % (self.value): (self.left, self.right)}, indent=1) # indent 格式上的缩进效果
class BinarySearchTree:
#创造根节点
def __init__(self, root=None):
self.root = root
def __str__(self):
return str(self.root)
def is_empty(self): # 用于判断节点是否存在 在下面可以直接调用函数
if self.root is None:
return True
else:
return False
def __insert(self, value):
new_node = Node(value, None)
if self.is_empty():
self.root = new_node
else:
parent_node = self.root # 从根节点的位置开始查找
while True:
# 当插入的值小于父节点的值得时候就判断左侧
if value < parent_node.value:
if parent_node.left is None: # 判断左侧节点是否为空
parent_node.left = new_node # 不为空插入值
break
else:
parent_node = parent_node.left #为空的话就节点下移
elif value >= parent_node.value: # 插入值大于父节点的值
if parent_node.right is None: # 判断右侧是否为空
parent_node.right = new_node # 如果为空就插入
break
else: # 不为空就进行节点下移的操作
parent_node = parent_node.right
new_node.parent = parent_node # 指定新节点的父节点
def insert(self, *args): # 调用上面的方法 ,参数是不定长参数,设置可以同时传入多个值
for value in args:
self.__insert(value)
return self
# 查询
def search(self,num):
if self.is_empty():
raise IndexError("么得元素")
else:
node = self.root
while node and node.value != num:
if num < node.value:
node = node.left
elif num >= node.value:
node = node.right
result = ""
if node :
result +=f"{num}这个数在里面"
else:
result +=f"{num}不在这个里面"
return node
a = BinarySearchTree()
print(a.insert(6, 1, 2, 1, 3, 6))
print(a.search(10))
删除树中的节点
思路:
首先需要找到节点,找到节点之后需要分情况:
1、没有孩子结点。
没有孩子节点只需要把当前节点变为空
2、只有左子树
只有左子树的时候就直接把左侧节点替换为当前节点的位置
3、只有右子树
思路同上
4、左右孩子都有
左右孩子都有的时候,需要查找左侧孩子中最大的值,来替换当前父节点的值
所以我们在书写删除remove函数的时候还需要添加两个函数,一个数用来重新定义节点的函数另一个是用来查找左子树最大值的函数。
"""
程序前半部分需要延续上面的插入和查找
"""
def is_right(self, node):
return node == node.parent.right
# 找父亲 找孩子
def __reassign_nodes(self, node, new_children):
if new_children is not None: # 如果当前结点子结点非空
new_children.parent = node.parent # 子结点和当前结点互换位置
if node.parent is not None: # 互换位置之后,如果当前结点有父结点
if self.is_right(node): # 判断当前结点是否为右侧结点
node.parent.right = new_children
else:
node.parent.left = new_children
else:
self.root = new_children
def remove(self, value):
node = self.search(value) # 查找节点
if node is not None:
if node.left is None and node.right is None: # 没有孩子节点的情况
self.__reassign_nodes(node, None) # 交换当前节点和None->当前节点变成空
elif node.left is None: # 只有右侧孩子节点
self.__reassign_nodes(node, node.right) #
elif node.right is None: # 只有左侧孩子节点
self.__reassign_nodes(node, node.left)
else: # 左右孩子结点都有
tmp_node = self.get_max(node.left) # 找到左子树的最大结点
self.remove(tmp_node.value) # 删除节点
node.value = tmp_node.value # 不改变树结构,只更改当前节点的值
def get_max(self, node=None):
if node is None:
node = self.root
if not self.is_empty():
while node.right is not None:
node = node.right
return node
树的遍历
可以从示意图中很轻松的看出遍历中的前中后说的是根节点在遍历中的位置,例如前序遍历:A–B--D–G--H–C--E–I--F 根节点始终在子节点的前方
前序遍历
# 第一种方法:递归调用,利用了栈的原理和方法 -- 程序依然需要连接上面的树代码 --
def preOrderTraverse(self, node):
if not node:
return None
print(node.value)
self.preOrderTraverse(node.left) #先遍历左侧,在遍历右侧
self.preOrderTraverse(node.right)
# 第二种方法: 栈的非递归实现,依然是利用了栈的方法,先把右侧压进去然后在压入左侧,先进入的是后弹出的元素
def preOrderTraverse2(self, node):
stack = [node]
while len(stack) > 0:
# print(node.value) # 每次都输出当前节点的值
if node.right:
stack.append(node.right)
if node.left:
stack.append(node.left)
node = stack.pop() # 弹出的是最后一个也就是左节点
中序遍历
左中右
# 第一种方法递归调用:
def preOrderTraverse(self, node):
if not node:
return None
self.preOrderTraverse(node.left)
print(node.value) # 在中间输出得到的结果就是中序遍历
self.preOrderTraverse(node.right)
# 第二种方法:非递归调用,利用了栈
def in_order_stack(self, node):
stack = []
while node or len(stack) > 0:
while node: # 先一路找左孩子,到最后知道在弹出,,弹出的时候顺序为当前最左侧节点,父亲节点,右节点
stack.append(node)
node = node.left
if len(stack) > 0:
node = stack.pop()
print(node.value)
node = node.right # 找右侧节点
后序遍历
左右中
# 思路:
def post_order_stack(self, node):
if node is None:
return False
stack1 = []
stack2 = []
stack1.append(node)
while stack1: # 找出后序遍历的逆序,存放在Stack2中
node = stack1.pop()
if node.left:
stack1.append(node.left)
if node.right:
stack1.append(node.right)
stack2.append(node)
while stack2: # 将stack2中的元素出栈,就是后序遍历
print(stack2.pop().vaule, end=" ")
层序遍历
按照上面的排序输出:A–B--C–D--E–F--G–H--I
# 在队列的基础上书写的层序遍历
def post_order_stack2(self,root:None):
from queue import Queue #导入队列的包
queue = Queue() #创建一个队列
queue.put(root) # 入队一个元素,将根节点入队
while not queue.empty():
node = queue.get()
print(node.value)
if node.left:
queue.put(node.left)
if node.right:
queue.put(node.right)
堆
二叉堆
概念:
本质上是一个完全二叉树,分为最大堆和最小堆
最大堆:
任何一个父节点的值都大于或等于他左右孩子的值
最小堆:
任何一个父节点的值都小于或等于他左右孩子的值
用python实现堆:
思想:假设一个父节点的下标是parent,左孩子的下标就是2 * parent + 1,右孩子的下标就是2 * parent,假设左孩子的下标是child,父节点的下标就是(child - 1)/2
class Heap:
def __init__(self):
"""
初始化一个空堆,使用数组来存放堆元素,节省存储
"""
self.data_list = []
def get_parent_init(self, index):
"""
返回父节点的下标
"""
# 索引或者索引超出数据范围之外
if index == 0 or index > len(self.data_list) - 1:
return None
else:
return (index - 1) >> 1 # 位的右移,相当于进行了下述的操作
# 如果某个几点的孩子空缺,数组对应的位置也空缺
# 假设一个父节点的下标是parent,左孩子的下标就是 2*parent +1 ,右孩子的下标是 2 * parent+1
# 左孩子的下标是child 父节点的下标是(child-1)/2
def swap(self, index_a, index_b):
"""
交换数组中的两个元素
"""
self.data_list[index_a], self.data_list[index_b] = self.data_list[index_b], self.data_list[index_a]
# 插入元素
def index(self,data):
"""
先把元素放在最后,然后在从后往前依次堆化
这里以大于顶堆为例 ,如果插入元素比父节点大,则交换,直到最后
"""
self.data_list.append(data)
index = len(self.data_list) - 1 # 新添加元素(最后一个)的索引
parent = self.get_parent_index(index)
# 循环,直到该元素成为堆顶,或小于父节点(对于大顶堆)
while parent is not None and self.data_list[parent] < self.data_list[index]:
self.swap(parent,index)
index = parent
parent = self.get_parent_index(parent) # 再次判断父节点
# 删除元素
def pop(self):
""""
删除第一个元素,然后再将最后一个元素放在堆顶,,再从上往下依次堆化
"""
remove_data = self.data_list
self.data_list[0] = self.data_list[-1]
self.heapify(0) # 从顶端堆化
return remove_data # 返回删除的元素
def heapify(self, index):
"""
从上往下堆化,从index开始堆化操作(大顶堆)
"""
total_index = len(self.data_list) - 1
while True:
maxvalue_index = index
if 2 * index + \
1 <= total_index and self.data_list[2 * index + 1] > self.data_list[maxvalue_index]:
maxvalue_index = 2 * index + 1 # 如果左孩子节点大于当前最大节点,最大索引值等于右孩子索引。
if 2 * index + \
2 <= total_index and self.data_list[2 * index + 2] > self.data_list[maxvalue_index]:
maxvalue_index = 2 * index + 2 # 如果右孩子节点大于当前最大节点,最大索引值等于右孩子索引。
if maxvalue_index == index:
break
self.swap(index, maxvalue_index) # 交换最大值和当前值
index = maxvalue_index # 当前值等于这一轮的最大值结点
排序
冒泡排序:
冒泡排序原理:
冒泡排序(Bubble Sort)也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢"浮"到数列的顶端。
python中代码实现:
a = [9, 8, 7, 3, 2, 1, 6, 5, 4, 30, 21, 65, 87, 53, 486,
13, 4861, 31, 68, 135, 131, 465, 163, 513, 143, 51, 61]
for i in range(0, len(a)):
print("第%s轮结果" % i, a)
for j in range(0, len(a) - i - 1): # 参照示意图可以知道每次循环之后最后一位都会是最大的值,所以要减 i
if a[j + 1] < a[j]:
a[j], a[j + 1] = a[j + 1], a[j]
# print(a)
选择排序
选择排序原理:
选择排序改进了冒泡排序,每次遍历列表只做一次交换,为了做到这一点,一个选择排序在遍历时寻找最大的值,并在完成遍历后,将其放到正确的地方。第二次遍历,找出下一个最大的值。遍历n-1次排序n个项,最终项必须在n-1次遍历之后
python代码实现
iList = [9, 8, 7, 3, 2, 1, 6, 5, 4]
if len(iList) <= 1:
print(iList)
#每次找出最小的一个放在最前面
for i in range(len(iList) - 1): # 每次都把最小的一个放在前面 ,到最后一个的时候自然就是最大的
minindex = i
for j in range(i + 1, len(iList)): #遍历循环知道最小值赋予给minindex
if iList[j] < iList[minindex]:
minindex = j
iList[i], iList[minindex] = iList[minindex],iList[i]
print(iList)
插入排序
插入排序原理:
插入排序(英语:Insertion Sort)是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
python程序实现
a = [9, 8, 7, 3, 2, 1, 6, 5, 4, 30, 21, 65, 87, 53, 486,
13, 4861, 31, 68, 135, 131, 465, 163, 513, 143, 51, 61]
def insertSort(iList):
if len(iList) == 0:
return iList
for right in range(1,len(iList)):
target = iList[right]
for left in range(left,right):
if iList[left] < iList[right]:
iList[left +1:right+1] = iList[left:right]
iList[left] = target
break
return iList
print(insertSort(a))
链表中的插入排序
class Node:
def __init__(self,data):
self.data = data
self.next = None
def insertionSortList(head):
dummy = Node(0)
# 创建一个新的链表(用来存储排好序的数字),pre作为一个虚拟节点,是排好序的链表的开头
pre = dummy
# cur是已经存在的链表的头部
cur = head
while cur is not None: # 将待排序的节点遍历
temp = cur.next
# 对已经排序好的链表进行遍历,找到插入节点的位置
while pre.next is not None and pre.next.data < cur.data:
pre = pre.next
# 已确定插入位置,完成插入 ##最重要的就是这四部
cur.next = pre.next
pre.next = cur
cur = temp
pre = dummy
return dummy.next
def printlink(head):
curr = head
while curr:
print(curr.data)
curr = curr.next
if __name__ == '__main__':
node1 = Node(3)
node2 = Node(5)
node3 = Node(4)
node4 = Node(2)
node5 = Node(6)
node6 = Node(1)
node7 = Node(7)
node1.next = node2
node2.next = node3
node3.next = node4
node4.next = node5
node5.next = node6
node6.next = node7
node7.next = None
insertionSortList(node1)
printlink(node1)
归并排序
归并排序原理:
是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法:
-
自上而下的递归(所有递归的方法都可以用迭代重写,所以就有了第 2 种方法);
-
自下而上的迭代;
先把一个数组拆分到最后剩下一个元素的时候,在一步一步向上合并,逐步排序
python实现
# 递归方法
# 传入左右拆分开的两个数组,在这里更多的利用了python的方法。
def merge(left,right):
arr = []
while left and right:
if left[0] >= right[0]:
arr.append(right.pop(0))
else:
arr.append(left.pop(0))
while left:
arr.append(left.pop(0))
while right:
arr.append(right.pop(0)) # extend() 函数用于在列表末尾一次性追加另一个序列中的多个值(用新列表扩展原来的列表)
return arr
# 传统方法 -- 双指针
# 这里传入的参数依然是左右两个拆分开的数组
def merge1(left,right):
left_len = len(left)
right_len = len(right)
mList = []
i = 0
j = 0
while i < left_len and j < right_len:
if left[i] <= right[j]: # 如果i小就追加到元素里面
mList.append(left[i])
i += 1
else:
mList.append(right[j])
j += 1
mList.extend(left[i:]) # 不管有没有元素都可以执行
mList.extend(right[j:])
return mList
# 逐步递归,将一个数组拆分到一个元素,然后再逐步合并排序
def mergeSort1(nums):
if len(nums) <= 1:
return nums
mid = len(nums) //2 # len(nums) >> 1 等价于整除的操作
left, right = nums[0:mid], nums[mid:]
return merge1(mergeSort(left), mergeSort(right))
def mergeSort1(nums):
if len(nums) <= 1:
return nums
mid = len(nums) //2 # len(nums) >> 1 等价于整除的操作
left, right = nums[0:mid], nums[mid:]
return merge2(mergeSort(left), mergeSort(right))
if __name__ == '__main__':
a = [5, 6, 8, 9, 7, 3, 6, 1, 3]
print(mergeSort1(a))
print(mergeSort2(a))
快速排序
快排基本原理:
- 1.先从数列中取出一个数作为基准数。
- 2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
- 3.再对左右区间重复第二步,直到各区间只有一个数。
python代码实现
单指针实现
# 单指针实现
def partition2(arr, start, end):
pivot = arr[start]
mark = start
for i in range(start+1, end+1):
if arr[i] < pivot:
mark += 1
arr[mark], arr[i] = arr[i], arr[mark]
arr[start] = arr[mark]
arr[mark] = pivot
return mark
def quickSort2(li, start, end):
if start >= end:
return
mid = partition2(li, start, end)
quickSort2(li, start, mid-1)
quickSort2(li, mid+1, end)
return li
print("快速排序(单指针遍历):")
print(quickSort2([4,7,3,5,6,2,8,1], 0, 7))
双指针实现
def swap(array, a, b):
array[a], array[b] = array[b], array[a]
def partition(li, start, end):
pivot = li[start]
p = start + 1
q = end
while p <= q:
print("p:" + str(p))
print("q:" + str(q))
while p <= q and li[p] < pivot:
p += 1
print("condition 1")
print("p:" + str(p))
print("q:" + str(q))
print(li)
while p <= q and li[q] >= pivot:
q -= 1
print("condition 2")
print("p:" + str(p))
print("q:" + str(q))
print(li)
if p < q:
swap(li, p, q)
print("condition 3")
print("p:" + str(p))
print("q:" + str(q))
print(li)
swap(li, start, q)
return q
def quickSort(li, start, end):
if start >= end:
return
mid = partition(li, start, end)
quickSort(li, start, mid-1)
quickSort(li, mid+1, end)
return li
print(quickSort([4,7,3,5,6,2,8,1], 0, 7))
计数排序
计数排序原理
计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
- (1)找出待排序的数组中最大和最小的元素
- (2)统计数组中每个值为i的元素出现的次数,存入数组C的第i项
- (3)对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
- (4)反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1
python实现
a = [9, 3, 5, 4, 9, 1, 2, 7, 8, 1, 3, 6, 5, 3, 4, 0, 10, 9, 7, 9]
def teachersCountSort(arr : List):
max_value = max(arr)
count_arr = [0] * (max_value +1)
for i in range(len(arr)):
count_arr[arr[i]] += 1
sort_arr = []
for i in range(len(count_arr)):
for j in range(count_arr[i]):
sort_arr.append(i)
return sort_arr
print(teachersCountSort(a))
桶排序
桶排序原理
桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。为了使桶排序更加高效,我们需要做到这两点:
- 在额外空间充足的情况下,尽量增大桶的数量
- 使用的映射函数能够将输入的 N 个数据均匀的分配到 K 个桶中
步骤:(与计数排序类似)
- 找到最大值最小值,确定每一个桶的区间范围,初始化桶的大小(或有几个数就创建几个桶,区间跨度 = (最大值-最小值)/(桶的数量-1))
- 遍历原始序列,把每个元素放到合适的桶中
- 合并各个桶
python中的实现
def ducketSort(arr):
max_value = max(arr)
min_value = min(arr)
d = max_value - min_value
# 1、初始化桶
bucket_num = len(arr)
count_list = []
for i in range(bucket_num):
count_list.append([]) # 创建桶
# 定位元素属于哪个桶
for i in range(len(arr)):
num = int((arr[i] - min_value) * (bucket_num - 1)/d)
bucket = count_list[num]
bucket.append(arr[i])
# 桶内排序
for i in range(len(count_list)):
count_list[i].sort()
# 按顺序输出
sort_arr = []
for sub in count_list:
for item in sub:
sort_arr.append(item)
return sort_arr
a = [9, 3, 5, 4, 9, 1, 2, 7, 8, 1, 3, 6, 5, 3, 4, 0, 10, 9, 7, 9]
print(ducketSort(a))