https://blog.csdn.net/weixin_45912307/article/details/115792813
python之数据结构与算法分析
01 数据结构与算法入门
1.1顺序表
根据线性表的实际存储方式,分为两种实现模型:
- 顺序表,将元素顺序地存放在一块连续的存储区里,元素间的顺序关系由它们的存储顺序自然表示。
- 链表,将元素存放在通过链接构造起来的一系列存储块中。
1.2 链表
1.2.1 单向链表
1. 单向链表(单链表) : 它的每个节点包含两个域,一个信息域(元素域)和一个连接域,这个链接指向链表中的下一个节点,而最后一个节点的链接则指向一个空值。
- 表元素elem用来存放具体数据
- 链接域next用来存放下一个节点的位置(python中的标识)
- 变量p指向链表的头节点(首节点)的位置,从p出发能找到表中的任意节点
2. 单链表的操作
- is_empty() 链表是否为空
- length() 链表长度
- travel() 遍历整个链表
- add(item) 链表头部添加元素
- append(item) 链表尾部添加元素
- insert(pos,item) 指定位置添加元素
- remove(item) 删除元素
- search(item) 查找节点是否存在
3. python实现单向链表
class Node(object):
def __init__(self, elem):
"""
:param elem: 表元素域
next:下一结点链接域
cursor(cur):游标
"""
self.elem = elem
# 定义next指向空
self.next = None
class SingleLinkList(object):
"""
单向链表也叫单链表,是链表中最简单的一种形式,它的每个节点包含两个域,一个信息域(元素域)和一个链接域。这个链接指向链表中的下一一个节点,而最后-个节点的链接域则指向一个空值。
表元素域elem用来存放具体的数据。
链接域next用来存放下一个节点的位置(python中的标识)
变量p指向链表的头节点(首节点)的位置,从p出发能找到表中的任意节点。
"""
def __init__(self, node=None):
self.__head = node # node.elem node.next
def is_empty(self):
"""链表是否为空 """
return self.__head is None
def length(self):
"""链表长度"""
# cur游标,用来移动遍历节点
cur = self.__head
count = 0
while cur is not None:
count += 1
cur = cur.next
# count 记录数量
return count
def travel(self):
"""遍历整个链表"""
cur = self.__head
while cur is not None:
print(cur.elem, end=' ')
cur = cur.next
def add(self, item):
"""链表头部添加元素:头插法"""
node = Node(item)
node.next = self.__head
self.__head = node
def append(self, item):
"""链表尾部添加元素:尾插法"""
node = Node(item)
# 下一结点链接域不为空
if self.is_empty():
self.__head = node
else:
cur = self.__head
while cur.next is not None:
cur = cur.next
cur.next = node
def insert(self, pos, item):
"""
pos: pos从0开始
pre:指定节点前一节点,相当于游标
node:插入的指定节点
指定位置添加元素
"""
# if pos<=0 头插法
if pos <= 0:
self.add(item)
# elif pos>(self.length()-1) 尾插法
elif pos > (self.length() - 1):
self.append(item)
# else 插入法
else:
pre = self.__head
count = 0
# 当循环退出后,pre指向pos-1
while count < (pos - 1):
count += 1
pre = pre.next
node = Node(item)
node.next = pre.next
pre.next = node
def remove(self, item):
"""删除元素"""
# 考虑删除头部、尾部、中间节点
cur = self.__head
pre = None
while cur is not None:
if cur.elem == item:
# 先判断是否是头节点
if cur == self.__head:
self.__head = cur.next
else:
pre.next = cur.next
break
else:
pre = cur
cur = cur.next
def search(self, item):
"""查找节点是否存在"""
# 1. 创建游标
cur = self.__head
# 2. 遍历游标
while cur is not None:
# 3. cur.elem = item
if cur.elem == item:
return True
else:
cur = cur.next
return False
if __name__ == '__main__':
ll = SingleLinkList()
ll.is_empty()
l1 = ll.length()
print(l1)
ll.append(55)
ll.is_empty()
l2 = ll.length()
print(l2)
ll.append(2)
ll.add(8)
ll.append(3)
ll.append(4)
ll.append(5)
# 55 1 8 2 3 4
ll.insert(-1, 9) # 9 8 55 2 1 8 2345
ll.insert(2, 100) # 9 8 100 55 2 1 8 2345
ll.travel()
1.2.2 双向链表
1. 双向链表定义: 一种更复杂的链表是“双向链表"或“双面链表"。每个节点有两个链接: 一个指向前一个节点,当此节点为第
一个节点时,指向空值;而另一个指向下一个节点,当此节点为最后一个节点时,指向空值。
2.双向链表操作:
- is_ empty()链表是否为空
- length(链表长度
- travel)遍历链表
- add(item)链表头部添加
- append(item)链表尾部添加
- insert(pos, item)指定位置添加
- remove(item)删除节点
- search(item)查找节点是否存在
3.python实现双向循环链表
class Node(object):
def __init__(self, elem):
"""
:param elem: 表元素域
next:下一结点链接域
cursor(cur):游标
"""
self.elem = elem
# 定义next指向空
self.next = None
# 定义next指向空
self.prev = None
class DoubleLinkList(object):
"""
一种更复杂的链表是“双向链表"或“双面链表"。每个节点有两个链接: 一个指向前一个节点,当此节点为第
一个节点时,指向空值;而另一个指向下一个节点,当此节点为最后一个节点时,指向空值。
"""
def __init__(self, node=None):
self._head = node # node.elem node.next
def is_empty(self):
"""链表是否为空 """
return self._head is None
def length(self):
"""链表长度"""
# cur游标,用来移动遍历节点
cur = self._head
count = 0
while cur is not None:
count += 1
cur = cur.next
# count 记录数量
return count
def travel(self):
"""遍历整个链表"""
cur = self._head
while cur is not None:
print(cur.elem, end=' ')
cur = cur.next
def add(self, item):
"""链表头部添加元素:头插法"""
node = Node(item)
# node的next指向_head
node.next = self._head
# _head指向新节点
self._head = node
node.next.prev = node
def append(self, item):
"""链表尾部添加元素:尾插法"""
node = Node(item)
# 下一结点链接域不为空
if self.is_empty():
self._head = node
else:
cur = self._head
while cur.next is not None:
cur = cur.next
cur.next = node
node.prev = cur
def insert(self, pos, item):
"""
pos: pos从0开始
pre:指定节点前一节点,相当于游标
node:插入的指定节点
指定位置添加元素
"""
# if pos<=0 头插法
if pos <= 0:
self.add(item)
# elif pos>(self.length()-1) 尾插法
elif pos > (self.length() - 1):
self.append(item)
# else 插入法
else:
cur = self._head
count = 0
# 当循环退出后,cur指向pos
while count < pos:
count += 1
cur = cur.next
# 当循环退出后,cur指向pos位置
node = Node(item)
# 方式1:
node.next = cur
node.prev = cur.prev
cur.prev.next = node
cur.prev = node
# 方式2:
# node.next = cur
# node.prev = cur.prev
# cur.prev = node
# node.prev.next = node
def remove(self, item):
"""删除元素"""
# 考虑删除头部、尾部、中间节点
cur = self._head
while cur is not None:
if cur.elem == item:
# 先判断是否是头节点
if cur == self._head:
self._head = cur.next
if cur.next: # 判断链表表是否只有一个节点
cur.next.prev = None
else:
cur.prev.next = cur.next
if cur.next: # 判断链表是否是最后一个节点
cur.next.prev = cur.prev
break
else:
cur = cur.next
def search(self, item):
"""查找节点是否存在"""
# 1. 创建游标
cur = self._head
# 2. 遍历游标
while cur is not None:
# 3. cur.elem = item
if cur.elem == item:
return True
else:
cur = cur.next
return False
if __name__ == '__main__':
DLL = DoubleLinkList()
DLL.is_empty()
l1 = DLL.length()
print(l1)
DLL.append(55)
DLL.is_empty()
l2 = DLL.length()
print(l2)
DLL.append(2)
DLL.add(8)
DLL.append(3)
DLL.append(4)
DLL.append(5)
# 55 1 8 2 3 4
DLL.insert(-1, 9) # 9 8 55 2 1 8 2345
DLL.insert(2, 100) # 9 8 100 55 2 1 8 2345
DLL.travel()
1.2.3 单向循环链表
1. 定义: 单链表的一个变形是单向循环链表,链表中最后一个节点的next域不再为none,而是指向链表的头节点
2. 单向循环链表操作:
- is_empty()判断链表是否为空
- length0返回链表的长度
- travel()遍历
- add(item)在头部添加一个节点
- append(item)在尾部添加一个节点
- insert(pos, item) 在指定位置pos添加节点
- remove(item)删除一个节点
- search(item)查找节点是否存在
3.python实现单向循环链表
class Node(object):
def __init__(self, elem):
"""
:param elem: 表元素域
next:下一结点链接域
cursor(cur):游标
"""
self.elem = elem
# 定义next指向空
self.next = None
class SingleCircularLinkList(object):
"""
单向循环链表:单链表的一个变形是单向循环链表,链表中最后一个节点的next域不再为none,而是指向链表的头节点
"""
def __init__(self, node=None):
self.__head = node # node.elem node.next
if node:
node.next = node
def is_empty(self):
"""链表是否为空 """
return self.__head is None
def length(self):
"""链表长度"""
if self.is_empty():
return 0
# cur游标,用来移动遍历节点
cur = self.__head
count = 1
while cur.next != self.__head:
count += 1
cur = cur.next
# count 记录数量
return count
def travel(self):
"""遍历整个链表"""
if self.is_empty():
return
cur = self.__head
while cur.next != self.__head:
print(cur.elem, end=' ')
cur = cur.next
# 退出循环,cur指向尾结点,但尾节点的元素未打印
print(cur.elem)
def add(self, item):
"""链表头部添加元素:头插法"""
node = Node(item)
if self.is_empty():
self.__head = node
node.next = node
else:
cur = self.__head
while cur.next != self.__head:
cur = cur.next
# 退出循环,cur指向尾结点
node.next = self.__head
self.__head = node
# 方式1:cur.next = node
cur.next = self.__head # 方式2
def append(self, item):
"""链表尾部添加元素:尾插法"""
node = Node(item)
# 下一结点链接域不为空
if self.is_empty():
self.__head = node
node.next = node
else:
cur = self.__head
while cur.next != self.__head:
cur = cur.next
# 方式1:
# node.next = cur.next
# cur.next = node
# 方式2:
cur.next = node
node.next = self.__head
def insert(self, pos, item):
"""
pos: pos从0开始
pre:指定节点前一节点,相当于游标
node:插入的指定节点
指定位置添加元素
"""
# if pos<=0 头插法
if pos <= 0:
self.add(item)
# elif pos>(self.length()-1) 尾插法
elif pos > (self.length() - 1):
self.append(item)
# else 插入法
else:
pre = self.__head
count = 0
# 当循环退出后,pre指向pos-1
while count < (pos - 1):
count += 1
pre = pre.next
node = Node(item)
node.next = pre.next
pre.next = node
def remove(self, item):
"""删除元素"""
# 考虑删除头部、尾部、中间节点
if self.is_empty():
return
cur = self.__head
pre = None
while cur.next != self.__head:
if cur.elem == item:
# 先判断是否是头节点
if cur == self.__head:
# 找到尾节点
rear = self.__head
while rear.next != self.__head:
rear = rear.next
self.__head = cur.next
rear.next = self.__head
else:
# 中间节点
pre.next = cur.next
return
else:
pre = cur
cur = cur.next
# 退出循环,cur指向尾结点
if cur.elem == item:
if cur == self.__head:
# 链表只有一个节点
self.__head = None
else:
pre.next = cur.next
def search(self, item):
"""查找节点是否存在"""
if self.is_empty():
return False
# 1. 创建游标
cur = self.__head
# 2. 遍历游标
while cur.next != self.__head:
# 3. cur.elem = item
if cur.elem == item:
return True
else:
cur = cur.next
# 对于最后一个元素或只有一个元素
if cur.elem == item:
return True
return False
if __name__ == '__main__':
ll = SingleCircularLinkList()
ll.is_empty()
l1 = ll.length()
print(l1)
ll.append(55)
ll.is_empty()
l2 = ll.length()
print(l2)
ll.append(2)
ll.add(8)
ll.append(3)
ll.append(4)
ll.append(5)
# 55 1 8 2 3 4
ll.insert(-1,9) # 9 8 55 2 1 8 2345
ll.insert(2,100) #9 8 100 55 2 1 8 2345
ll.travel()
02 栈与队列
2.1 栈
1.栈的定义(LIFO:last in First Out): 栈是一种操作受限制的线性表,将允许进行插入、删除、访问元素的一端称为栈顶,另一端称为栈底。
2.栈支持的操作:
- Stack():创建一个空栈。它不需要参数,且会返回一个空栈;
- s.isEmpty() 检查栈是否为空,它不需要参数,且会返回一个布尔值;
- s.push(item):将一个元素添加到栈的顶端。它需要一个参数item,且无返回值;
- s .peek() 返回栈顶端的元素,但是并不移除该元素。它不需要参数,也不会修改栈的内容;
- s.size() 返回栈中元素的数目。它不需要参数,且会返回一个整数;
- s.pop():将栈顶端的元素移除。它不需要参数,但会返回顶端的元素并且修改栈的内容;
3.python实现堆栈
class Stack():
"""
栈方式1:假设列表的尾部是栈的顶端。当栈增长时(即进行push操作), 新的元素会被添加到列表的尾部。pop操作同样会修改这一端。
将允许进行插入、删除的一端称为栈顶,另一端称为栈底。
"""
# 创建一个空栈
def __init__(self):
self.__list = []
# 检查栈是否为空:判断是否等于一个空列表
def isEmpty(self):
return self.__list == []
# return not self.__list
# 统计栈的长度
def size(self):
return len(self.__list)
# 返回栈顶元素
def peek(self):
# return self.__list[len(self.__list)-1]
if self.__list:
return self.__list[-1]
else:
return None
# 入栈(把列表尾部假设为栈顶)
def push(self, item):
self.__list.append(item)
# 出栈
def pop(self):
return self.__list.pop()
class Stack2:
"""
栈方式2:选择将列表头部作为顶端,必须用pop方法和insert方法显式地访问下标为0的元素,即列表中的第一个元素;
"""
def __init__(self):
self.__list = []
def isEmpty(self):
"""判断栈是否为空"""
# return self.__list == []
return not self.__list
def size(self):
"""返回栈的元素个数"""
return len(self.__list)
# 入栈
def push(self, item):
"""添加一个新的元素item到栈顶"""
self.__list.insert(0, item)
# 出栈
def pop(self):
return self.__list.pop(0)
def peek(self):
"""返回栈顶元素"""
if self.__list:
return self.__list[0]
else:
return None
if __name__ == '__main__':
s = Stack2()
print(s.isEmpty())
s.push('a')
s.push('b')
s.push('小明')
s.push('c')
print(s.size())
print(s.pop())
print(s.pop())
print(s.pop())
print(s.pop())
2.2 队列
1. 队列定义(FIFO:First In First Out): 队列是一种特殊的线性表,只允许在表的前端进行删除操作,而在表的后端进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头;
2. 队列支持的操作:
- Queue()创建一个空队列。它不需要参数,且会返回一个空队列。
- enqueue(item)在队列的尾部添加一个元素。它需要一个 元素作为参数,不返回任何值。
- dequeue()从队列的头部移除一个元素。它不需要参数,且会返回一个元素,并修改队列的内容。
- isEmpty()检查队列是否为空。它不需要参数,且会返回一个布尔值。
- size()返回队列中元素的数目。它不需要参数,且会返回一个整数。
- dequeue()从队列的头部移除一个元素。 它不需要参数,且会返回一个元素,并修改队 列的内容。
3.python实现队列:
class Queue1:
"""
队列方式1:假设列表头部为队头:append向队列尾部添加元素,pop(0)移除队列头部元素
"""
def __init__(self):
self.__list = []
def isEmpty(self):
return self.__list == []
def size(self):
return len(self.__list)
# 队尾插入元素
def enqueue(self, item):
self.__list.append(item)
# 队头删除元素
def dequeue(self):
return self.__list.pop(0)
class Queue2:
"""
假设列表头部(位置0)为队尾:insert向队列尾部添加元素O(n),pop移除队列头部元素O(1)
"""
def __init__(self):
self.__list = []
def isEmpty(self):
return self.__list == []
def size(self):
return len(self.__list)
# 队列尾部插入元素
def enqueue(self, item):
self.__list.insert(0, item)
# 把队头删除的元素返回
def dequeue(self):
return self.__list.pop()
if __name__ == '__main__':
q = Queue2()
print(q.isEmpty())
q.enqueue(10)
q.enqueue(100)
q.enqueue(1000)
q.enqueue(10000)
print(q.size())
print(q.dequeue()) # 10
print(q.dequeue()) # 100
print(q.dequeue()) # 1000
print(q.dequeue()) # 10000
print(q.size())
2.3 双端队列
1. 双端队列定义: 具有队列和栈的性质的数据结构。双端队列中的元素可以从两端弹出,其限定插入和删除操作在表的两端进行。双端队列可以在队列任意一端入队和出队。
2. 双端队列原理图:
3.双端队列支持的操作:
- Deque()创建一个空的双端队列。它不需要参数,且会返回一个空的双端队列。
- addFront(item)将一个元素添加到双端队列的前端。它接受一个元素作为参数,没有返回值
- addRear(item)将一个元素添加到双端队列的后端。它接受一个元素作为参数,没有返回值。
- removeFront()从双端队列的前端移除一个元素。它不需要参数,且会返回一个元素,并修改双端队列的内容。
- removeRear()从双端队列的后端移除一个元素。它不需要参数,且会返回一个元素,并修改双端队列的内容。
- isEmpty()检查双端队列是否为空。它不需要参数,且会返回一个布尔值。
- size()返回双端队列中元素的数目。它不需要参数,且会返回一个整数。
4.python实现双端队列:
# -- encoding: utf-8 --
# @time: 2021/4/18 11:49
# @Author: jsonLiu
# @Email: xxxxxxxxx@qq.com
# @file: deque
# 双端队列
"""
- Deque()创建一个空的双端队列。它不需要参数,且会返回一个空的双端队列。
- addFront(item)将一个元素添加到双端队列的前端。它接受一个元素作为参数,没有返回值
- addRear (item)将一个元素添加到双端队列的后端。它接受一个元素作为参数,没有返回值。
- popFront()从双端队列的前端移除一个元素。它不需要参数,且会返回一个元素,并修改双端队列的内容。
- popRear()从双端队列的后端移除一个元素。它不需要参数,且会返回一个元素,并修改双端队列的内容。
- isEmpty()检查双端队列是否为空。它不需要参数,且会返回一个布尔值。
- size()返回双端队列中元素的数目。它不需要参数,且会返回一个整数。
"""
class Deque1:
"""假设双端队列的后端是列表位置0处"""
def __init__(self):
self.__list = []
def isEmpty(self):
# return self.__list == []
return not self.__list
def size(self):
return len(self.__list)
def add_front(self, item):
"""添加到双端队列前端"""
self.__list.append(item)
def add_rear(self, item):
"""添加到双端队列后端"""
self.__list.insert(0, item)
def pop_front(self):
"""从双端队列的前端移除一个元素"""
return self.__list.pop(len(self.__list) - 1)
# return self.__list.pop()
def pop_rear(self):
"""双端队列的后端移除一个元素"""
return self.__list.pop(0)
class Deque2:
"""假设双端队列的前端是列表位置0处"""
def __init__(self):
self.__list = []
def isEmpty(self):
# return self.__list == []
return not self.__list
def size(self):
return len(self.__list)
def add_front(self, item):
"""添加到双端队列前端"""
self.__list.insert(0, item)
def add_rear(self, item):
"""添加到双端队列后端"""
self.__list.append(item)
def pop_front(self):
"""从双端队列的前端移除一个元素"""
return self.__list.pop(0)
def pop_rear(self):
"""双端队列的后端移除一个元素"""
# return self.__list.pop()
return self.__list.pop(len(self.__list) - 1)
if __name__ == '__main__':
d = Deque2()
# d = Deque1()
print(d.isEmpty()) # True
d.add_rear(4)
d.add_rear(3)
d.add_front(2)
d.add_front(1)
print(d.size()) # 4
d.add_rear(5)
print(d.pop_rear())
print(d.pop_front())
03 排序与搜索
3.1 排序
1.冒泡排序:
- 它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成
2.选择排序
- 第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序序列的末尾。直到所有元素均排序完毕
3.插入排序:
- 通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描, 找到相应位置并插入。插入排序在实现上,在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
4.快速排序
- 通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列
5.希尔排序 (插入排序改进版)
- 先将要排序的一组数按某个增量d分成若干组,每组中记录的下标相差d,对每组中全部元素进行排序,然后再用一个较小的增量对它进行分组,在每组中再进行排序。当增量减到1时,整个要排序的数被分成一组,排序完成
6.归并排序
- 先递归分解数组,再合并数组。基本思路是比较两个数组的最前面的数, 谁小就先取谁,取了后相应的指针就往后移一位。然后再比较,直至一个数组为空,最后把另一个数组的剩余部分复制过来即可。
python实现排序
# -- encoding: utf-8 --
# @time: 2021/4/18 12:30
# @Author: jsonLiu
# @Email: xxxxxxxxx@qq.com
# @file: sort_algorithm
# 1. 冒泡排序 最坏时间复杂度n^2,最优时间复杂度O(n)
def bubble_sort(alist):
if isinstance(alist, tuple):
alist = list(alist)
for i in range(len(alist) - 1): # len(alist)-1比较的轮数
count = 0
for j in range(len(alist) - 1 - i): # len(alist)-1-i每轮需要比较的次数
if alist[j] > alist[j + 1]:
alist[j + 1], alist[j] = alist[j], alist[j + 1]
count += 1
if 0 == count: # 当为有序序列时,时间复杂度为O(n)
return
# 2. 选择排序 最优时间复杂度/最坏时间复杂度 n^2
def selection_sort(alist):
# 假设位置0为最小值
n = len(alist)
for j in range(n - 1): # j:0~n-2
min_index = j
for i in range(j + 1, n):
if alist[i] < alist[min_index]:
min_index = i
alist[j], alist[min_index] = alist[min_index], alist[j]
# 3. 插入排序:最坏时间复杂度O(n^2) 最优时间复杂度O(n)
def insert_sort(alist):
n = len(alist)
# 外层循环:从右边的无序序列中取出多少个元素执行这样的过程
for j in range(1, n):
i = j
# 内层循环:执行从右边的无序序列中取出第一个元素,即i位置的元素,然后将其插入到前面的正确位置
while i > 0:
if alist[i - 1] > alist[i]:
alist[i], alist[i - 1] = alist[i - 1], alist[i]
i -= 1
else:
break
# 4. 快速排序 最优时间复杂度nlogn 最坏时间复杂度n^2
def quick_sort(alist, first, last):
if first >= last:
return
mid_value = alist(first)
low = first
high = last
while low < high:
while low < high and alist[high] >= mid_value:
high -= 1
alist[low] = alist[high]
while low < high and alist[low] < mid_value:
low += 1
alist[high] = alist[low]
# 从顺序退出时,low == high
alist[low] = mid_value
# 对low左边列表执行快速排序
quick_sort(alist, first, low - 1)
# 对low右边列表排序
quick_sort(alist, low + 1, last)
# 5.希尔排序
def shell_sort(alist):
n = len(alist)
gap = n // 2
while gap > 0:
# 与普通插入算法区别就是gap步长
for j in range(gap, n):
i = j
while i > 0:
if alist[i] < alist[i - gap]:
alist[i], alist[i - gap] = alist[i - gap], alist[i]
i -= gap
else:
break
gap //= 2 # gap除以2,缩短步长
# 6.归并算法
def merge_sort(alist):
n = len(alist)
if n <= 1:
return alist
mid = n // 2
# left 采用归并排序后形成的有序的新的列表
left_li = merge_sort(alist[:mid])
# right 采用归并排序后形成的有序的新的列表
right_li = merge_sort(alist[mid:])
# 将两个有序的子序列合并为一个新的整体
# merge_sort(left,right)
left_pointer, right_pointer = 0, 0
new_l = []
while left_pointer < len(left_li) and right_pointer < len(right_li):
if left_li[left_pointer] < right_li[right_pointer]:
new_l.append(left_li[left_pointer])
left_pointer += 1
else:
new_l.append(right_li[right_pointer])
right_pointer += 1
# 反之左边或右边列表最后一个值没有append
new_l += left_li[left_pointer:]
new_l += right_li[right_pointer:]
return new_l
if __name__ == '__main__':
# s = 8,5,10,9,2
# l = bubble_sort(s)
# print(l)
# s = [17,20,93,54,77,31,44]
# l = selection_sort(s)
# print(l)
# s = [20, 17, 93, 54, 77, 31, 44]
# insert_sort(s)
# print(s)
# s = [20, 17, 93, 54, 77, 31, 44, 54]
# insert_sort(s)
# print(s)
# s = [20, 17, 93, 54, 77, 31, 44, 54]
# shell_sort(s)
# print(s)
l = [20, 17, 93, 54, 77, 31, 44, 54]
print(l)
new_l = merge_sort(l)
print(new_l)
3.2 搜索
3.2.1顺序搜索
1.无序列表顺序搜索: 从列表中的第一个元素开始,沿着默认的顺序逐个查看,知道找到目标元素或者查完列表。如果查完列表后仍没有找到目标元素,则说明目标元素不在列表中。
2.有序列表顺序搜索: 假设列表中的元素按升序排列。如果存在目标元素,那么它出现在n个位置中任意一个位置的可能性仍然一样大,因此比较次数与在无序列表中相同。如果不存在目标元素,那么搜索效率就会提高;
def UnsequentialSearch(ulist, item):
"""
这个函数接受列表与目标元素作为参数, 并返回一个表示目标元素是否存在的布尔值。布尔型变量found的初始值为False, 如果找到目标元素,就将它的值改为Tru
"""
pos = 0
found = False
while pos < len(ulist) and not found:
if ulist[pos] == item:
found = True
else:
pos += 1
return found
def OrderedListSequentialSearch(ulist,item):
pos = 0
found = False
stop = False
while pos < len(ulist) and not found and not stop:
if ulist[pos] == item:
found = True
else:
if ulist[pos] > item:
stop = True
else:
pos = pos+1
return found
if __name__ == '__main__':
# ret = UnsequentialSearch([1, 3, 10, 5, 8], 7)
# print(ret)
ret = OrderedListSequentialSearch([1, 3, 5, 7, 10], 6)
print(ret)
3.2.2.二分查找
1.二分查找定义
- 二分查找又称折半查找,优点是比较次数少,查找速度快,平均性能好;
- 其缺点是要求待查表为有序表,且插入删除困难;
- 折半查找方法适用于不经常变动而查找频繁的有序列表。
- 首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;
- 否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。
2.python实现二分查找:
"""
应用前提:在一个含有n个元素的有序序列中定位目标值 时间复杂度:O(logn)
该算法维持两个参数low和high,这样可使所有候选条目的索引位于low和high之间。首先, low=0和high=n-1。然后我们比较目标值和中间值候选项,即索引项[mid]的数据。
mid =L(low + high)/2 ]考虑以下三种情况:
- 如果目标值等于[mid]的数据, 然后找到正在寻找的值,则查找成功并且终止。
- 如果目标值< [mid] 的数据, 对前半部分序列重复这一过程,即索引的范围从low到mid-1.
- 如果目标值> [mid] 的数据,对后半部分序列重复这一过程,即索的范围从mid+1到high。
- 如果low >high,说明索引范围[low, high]为空,则查找不成功。该算法被称为二分查找
"""
def binary_search(alist, item):
"""非递归"""
first = 0
last = len(alist) - 1
found = False
while first <= last and not found:
mid = (first + last) // 2
if alist[mid] == item:
found = True
else:
if item < alist[mid]:
last = mid - 1
else:
first = mid + 1
return found
def binary_search_recursion(alist, item):
if len(alist) > 0:
mid = len(alist) // 2
if alist[mid] == item:
return True
elif item < alist[mid]:
return binary_search_recursion(alist[:mid], item)
else:
return binary_search_recursion(alist[mid + 1:], item)
return False
if __name__ == '__main__':
ret = binary_search_recursion([17, 20, 26, 31, 44, 54, 55, 65, 77, 69], 26)
print(ret)
ret = binary_search([17, 20, 26, 31, 44, 54, 55, 65, 77, 69], 68)
print(ret)
3.2.3哈希散列
1.映射支持的操作
- Map() 创建一个空的映射,他返回一个空的映射集合
- put(key, val)往映射中加入一个新的键值对。如果键已经存在,就用新值替换旧值。
- get(key)返回key对应的值。如果key不存在,则返回None。
- del通过del map[key]这样的语句从映射中删除键-值对。
- len()返回映射中存储的键-值对的数目。
- in通过key in map这样的语句,在键存在时返回True,否则返回False。
2. python实现映射
class Map(object):
def __init__(self,size=11):
self.size = size
self.__slots = [None] * self.size
self.__data = [None] * self.size
def put(self, key, val):
hashvalue = self.hashfunction(key, len(self.__slots))
if self.__slots[hashvalue] == None:
self.__slots[hashvalue] = key
self.__data[hashvalue] = val
else:
if self.__slots[hashvalue] == key:
self.__data[hashvalue] = val
else:
nextslot = self.rehash(hashvalue, len(self.__slots))
while self.__slots[nextslot] != None and self.__slots[nextslot] != key:
nextslot = self.rehash(nextslot, len(self.__slots))
if self.__slots[nextslot] == None:
self.__slots[nextslot] = key
self.__data[nextslot] = val
else:
self.__data[nextslot] = val
def get(self, key):
startslot = self.hashfunction(key, len(self.__slots))
data = None
stop = False
found = False
position = startslot
while self.__slots[position] != None and \
not found and not stop:
if self.__slots[position] == key:
found = True
data = self.__data[position]
else:
position = self.rehash(position, len(self.__slots))
if position == startslot:
stop = True
return data
def delete(self,key):
pass
def __getitem__(self, key):
return self.get(key)
def __setitem__(self, key, val):
self.put(key, val)
def __delitem__(self, key):
self.delete(key)
def len(self):
pass
def hashfunction(self, key, size):
return key % size
def rehash(self, oldhash, size):
return (oldhash + 1) % size
04 树与算法
4.1树的定义
树(tree)是一种抽象数据类型〈ADT)或是实作这种抽象数据类型的数据结构,用来模拟具有树状结构性质的数据集食。它是由n (n>=1)个有限节点组成一个具有层次关系的集合。把它叫做"树"是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点∶
- 每个节点有零个或多个子节点;
- 没有父节点的节点称为根节点;
- 每一个非根节点有且只有一个父节点;
- 除了根节点外,每个子节点可以分为多个不相交的子树;
4.2 树的术语
- 节点的度:一个节点含有的子树的个数称为该节点的度;
- 树的度:一棵树中,最大的节点的度称为树的度;
- 叶节点或终端节点︰度为零的节点;
- 父亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;
- 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点;
- 兄弟节点:具有相同父节点的节点互称为兄弟节点;
- 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
- 树的高度或深度︰树中节点的最大层次;
- 堂兄弟节点︰父节点在同一层的节点互为堂兄弟;
- 节点的祖先:从根到该节点所经分支上的所有节点;
- 子孙:以某节点为根的子树中任一节点都称为该节点的子孙。
- 森林:由m(m>=O)棵互不相交的树的集合称为森林;
4.3树的种类
- 无序树:树中任意节点的子节点之间没有顺序关系,这种树称为无序树,也称为自由树;
- 有序树:树中任意节点的子节点之间有顺序关系,这种树称为有序树;
- 二叉树:每个节点最多含有两个子树的树称为二叉树;
- 完全二叉树:对于一颗二叉树,假设其深度为d(d>1)。除了第d层外,其它各层的节点数目均已达最大值,且第d层所有节点从左向右连续地紧密排列,这样的二叉树被称为完全二叉树,其中满二叉树的定义是所有叶节点都在最底层的完全二叉树;
- 平衡二叉树(AVL树) :当且仅当任何节点的两棵子树的高度差不大于1的二叉树;
- 排序二叉树(二叉查找树(英语: Binary Search Tree) ,也称二叉搜索树、有序二叉树) ;
- 霍夫曼树(用于信息编码) :带权路径最短的二叉树称为哈夫曼树或最优二叉树;
- B树: 一种对读写操作进行优化的自平衡的二叉查找树,能够保持数据有序,拥有多余两个子树。
4.4 树的存储方式
- 顺序存储:将数据结构存储在固定的数组中,然在遍历速度上有一定的优势,但因所占空间比较大,是非主流二叉树。二叉树通常以链式存储。
- 链式存储:
4.5 树的常见应用
- 1.xml,html
- 2.路由协议
- 3.mysq|数据库索引
- 4.文件系统的目录结构
- 5.decision
4.6 二叉树
4.6.1 二叉树性质
- 性质1:在二叉树的第i层上至多有2^(i-1)个结点(i>0)
- 性质2:深度为k的二叉树至多有2^k- 1个结点(k>0)
- 性质3:对于任意一棵二叉树,如果其叶结点数为N0,而度数为2的结点总数为N2,则N0=N2+1;
- 性质4:具有n个结点的完全二叉树的深度必为log2(n+1)
- 性质5:对完全二叉树,若从上至下、从左至右编号,则编号为i的结点,其左孩子编号必为2i,其右孩子编号
必为2i+1;其双亲的编号必为i/2 (i=1 时为根,除外)
4.6.2 二叉树支持的操作
BinaryTree()
创建一个二叉树实例。getLeftChild()
返回当前节点的左子节点所对应的二叉树。getRightChild()
返回当前节点的右子节点所对应的二叉树。setRootval(val)
在当前节点中存储参数val中的对象。getRootval()
返回当前节点存储的对象。insertLeft(val)
新建一棵二叉树,并将其作为当前节点的左子节点。insertRight (val)
新建一棵二叉树,并将其作为当前节点的右子节点。
4.6.3 二叉树深度遍历(三种方式)
- 层次(从上往下从左往右)
- 先序(先根再左后右):前序遍历在前序遍历中,先访问根节点,然后递归地前序遍历左子树,最后递归地前序遍历右子树。
- 中序(左中右):在中序遍历中,先递归地中序遍历左子树,然后访问根节点,最后递归地中序遍历右子树。
- 后序(左右中)先遍历左子树,然后遍历右子树,最后访问根结点,在遍历左、右子树时,仍然先遍历左子树,然后遍历右子树,最后遍历根结点
4.6.4 python实现二叉树
class Node(object):
def __init__(self,item):
self.elem = item
self.rchild = None
self.lchild = None
class BinaryTree(object):
def __init__(self):
self.root = None
def add(self,item):
# 添加元素操作
node = Node(item)
if self.root == None:
self.root = node
return
queue = [self.root]
while queue:
cur_node = queue.pop(0)
if cur_node.lchild is None:
cur_node.lchild = node
return
else: # 左子节点存在则append追加
queue.append(cur_node.lchild)
if cur_node.rchild is None:
cur_node.rchild = node
return
else:
queue.append(cur_node.rchild)
# 广度遍历(层次遍历)
def breadth_travel(self):
queue = [self.root]
if self.root is None:
return
while queue:
cur_node = queue.pop(0)
print(cur_node.elem,end=" ")
if cur_node.lchild is not None:
queue.append(cur_node.lchild)
if cur_node.rchild is not None:
queue.append(cur_node.rchild)
def preorder(self,node):
"""先序遍历"""
if node is None:
return
print(node.elem,end=" ")
self.preorder(node.lchild)
self.preorder(node.rchild)
def inorder(self,node):
"""中序遍历"""
if node is None:
return
self.inorder(node.lchild)
print(node.elem, end=" ")
self.inorder(node.rchild)
def postorder(self,node):
"""后序遍历"""
if node is None:
return
self.postorder(node.lchild)
self.postorder(node.rchild)
print(node.elem, end=" ")
if __name__ == '__main__':
tree = BinaryTree()
tree.add(1)
tree.add(3)
tree.add(5)
tree.add(7)
tree.add(9)
tree.add(11)
tree.breadth_travel() # 层次:1 3 5 7 9 11
print("\n")
tree.preorder(tree.root) # 先序 1 3 7 9 5 11
print("\n")
tree.inorder(tree.root) # 中序:7 3 9 1 11 5
print("\n")
tree.postorder(tree.root) # 后序:7 9 3 11 5 1
转载:https://blog.csdn.net/weixin_45912307/article/details/115792813