数据结构与算法
1 递归
1.1 递归条件
递归条件: 结束条件+调用自身
def fun1(x):
if x>0:
print(x)
fun1(x-1)
fun1(3)
3
2
1
def fun2(x):
if x>0:
fun2(x-1)
print(x)
fun2(3)
1
2
3
1.2 汉诺塔
有三根柱子,其中一根上从下往上按照大小顺序油64个圆盘。要求把圆盘从一根柱子移动到另外一根上,要求小圆盘不能放在大圆盘上,在三根柱子上一次只能移动一个圆盘。
解题思路:
- 把n-1个圆盘从A经过C移动到B
- n从A移动到C
- n-1从B经过A移动到C
# 1 Function
def hanoi(n,a,b,c):
count = 0
if n>0:
hanoi(n-1,a,c,b)
t.append('moving from %s to %s'%(a,c))
print('moving from %s to %s'%(a,c))
hanoi(n-1,b,a,c)
# 2 Test
hanoi(3,'A','B','C')
moving from A to C
moving from A to B
moving from C to B
moving from A to C
moving from B to A
moving from B to C
moving from A to C
# 3 查看移动次数
aa = []
def hanoi(n,a,b,c):
count = 0
if n>0:
hanoi(n-1,a,c,b)
t = 'moving from %s to %s'%(a,c)
aa.append(t)
print('moving from %s to %s'%(a,c))
hanoi(n-1,b,a,c)
hanoi(3,'A','B','C')
len(aa)
# 4 递推公式 h(x)=2h(x-1)+1,根据递推公式计算h(x)?
def hanota_count(x):
if x == 1:
return 1
elif x>1:
return 2*hanota_count(x-1)+1
# 5 用非递归的方法计算汉诺塔移动次数
# 设置一个列表,把每一次的结果记录,直到计算到第n次
def c1(n):
a = []
a.append(1)
if n==1:
return a[-1]
else:
for i in range(n-1):
t = 2*a[-1]+1
a.append(t)
return a[-1]
# 6 设置一个指针
# n 1 2 3 4 5 6 7 8
# c 1 3 7
def c2(n):
a =7
if n == 1:
return 1
else:
for i in range(n-3):
b = 2*a+1
a = b
return a
c2(64)
18446744073709551615
2 查找
2.1 顺序查找
从列表的第一个元素顺序查找,如果找到元素就返回该元素的下标,如果遍历完列表而没有查找到该元素就返回。
# 时间复杂度o(n) search.py
def linear_search(dataset,value):
for i in range(len(dataset)):
if value == dataset[i]:
return i
return print('查无此数')
# Test
li = list(range(1000000))
linear_search(li,999999)
2.2 二分查找
二分查找是对有序列表在[0:n]之间,目标元素和中间元素对比确定元素在列表的哪一半,直到找到该元素。
# 时间复杂度o(log(n)) search.py
a = [0,1,2,3,4,5,6,7,8,9]
def binary_search(list,value):
left = 0
right = len(list)-1
while(left <= right):
mid = (left+right) // 2
if value == list[mid]:
return mid
elif value > list[mid]:
left = mid + 1
else:
right = mid - 1
else:
return None
a = [0,1,2,3,4,5,6,7,8,9] 3
l m r
l m r
(lm)r
(lmr)
# Test
li = list(range(1000000))
binary_search(li,999999)
3 列表排序
冒泡排序、选择排序、插入排序、快速排序、堆排序、归并排序、希尔排序、计数排序、基数排序
3.1 冒泡排序
冒泡排序:对元素两两排序,如果前面的数大于后面的数,则对两个数据交换,直到遍历(n-1)个数据。
def bubble_sort(li):
n = len(li)
for i in range(n-1):
for j in range(n-i-1):
if li[j] > li[j+1]:
li[j] , li[j+1] = li[j+1] , li[j]
return li
li = [random.randint(0,100000) for i in range (100)]
bubble_sort(li)
# 冒泡排序改进版
def bubble_sort2(li):
n = len(li)
flag = False
for i in range(n-1):
for j in range(n-i-1):
if li[j] > li[j+1]:
li[j] , li[j+1] = li[j+1] , li[j]
flag = True
print(li)
if not flag:
return li
return li
li = [random.randint(0,100000) for i in range (100)]
bubble_sort2(li)
3.2 选择排序
选择排序:每次找到比当前值小的数据并调换位置,直到遍历所有数据。
def select_sort(li):
n = len(li)
for i in range(n-1):
min = i
for j in range(i+1,n):
if li[j]<li[min]:
min = j
if min != i:
li[i],li[min] = li[min],li[i]
return li
import random
li = [random.randint(0,10) for i in range(10)]
print(li)
select_sort(li)
3.3 插入排序
插入排序:遍历所有数据,如果当前数据小于之前的数据,调换元素位置,直到不满足条件。
def insert_sort(li):
n = len(li)
for i in range(1,n):
while((i>0) & (li[i-1]>li[i]) ):
li[i-1],li[i] = li[i],li[i-1]
print(li)
i -= 1
print()
print(li)
import random
li = [random.randint(0,10) for i in range(10)]
insert_sort(li)
3.4 快速排序
快速排序:任取一个元素p,选择元素使得元素p左边的元素都比p小,右边的元素都比p大,然后递归完成排序。
def partition(li,left,right):
tmp = li[left]
while(left< right):
while((left< right) & (li[right] >= tmp)):
right -= 1
li[left] = li[right]
while((left< right) & (li[left] <= tmp)):
left += 1
li[right] = li[left]
li[left] = tmp # 当left==right
return left
def quick_sort(li,left,right):
if left < right:
mid = partition(li,left,right)
quick_sort(li,left,mid-1)
quick_sort(li,mid+1,right)
# Test
li=[5,9,4,2,7,1,6,8,3]
import random
li = [random.randint(0,10) for i in range(10)]
quick_sort(li,0,len(li)-1)
3.5 堆排序
def sift(data,low,high): # sift:向下调整过程,把小值撸下来,O(logn)
i = low
j = 2*i + 1 # 左节点
tmp = data[i] # 堆顶
while j <= high:
if j < high and data[j] < data[j+1]: # 找出子树中较大的一个值;
j += 1
if tmp < data[j]: # 比较父节点和子节点的大小,
data[i] = data[j] # 把子节点放在父节点上
i = j # 指针下移
j = 2*i + 1
else: # tmp > data[j]
data[i] = tmp
break # 如父节点的值大,跳出循环。
else:
data[i] = tmp # 把最大值
def heap_sort(data): # 堆排序
n = len(data)
for i in range((n-1-1)//2,-1,-1): # 建立堆, 遍历父节点 O(nlogn)
sift(data,i,n-1)
for i in range(n-1,-1,-1): # 向下调整,最大数依次弹出,取出第二大的数放在堆顶。
data[0], data[i] = data[i], data[0] # 交换
sift(data, 0, i-1) # 调整
import random
li = [i for i in range(10)]
random.shuffle(li)
print(li)
heap_sort(li)
print(li)
# python heap_sort模块
heapq.heapify(x)
heapq.heappush(heap,item)
heapq.heappop(heap)
import heapq # q=queue
import random
li = [i for i in range(10)]
heapq.heapify(li) # 建堆
heapq.heappop(li) # 堆排序,弹出最小元素
堆排序–topk问题: 有n个数,取前k个最大的数
取出列表前k个元素,建立一个小根堆,堆顶就是目前第k大的数。
依次向后遍历列表,对于列表中的元素,如果小于堆顶,则忽略该元素,如果大于堆顶,则将堆顶更换为该元素,并对堆进行一次调整。
遍历列表所有元素后,倒序弹出堆顶。
# O(mlogk)
def sift(data,low,high): # sift:向下调整过程,把小值撸下来,O(logn)
i = low
j = 2*i + 1 # 左节点
tmp = data[i] # 堆顶
while j <= high:
if j < high and data[j] > data[j+1]: # 找出子树中较大的一个值;
j += 1
if tmp > data[j]: # 比较父节点和子节点的大小,
data[i] = data[j] # 把子节点放在父节点上
i = j # 指针下移
j = 2*i + 1
else: # tmp > data[j]
data[i] = tmp
break # 如父节点的值大,跳出循环。
else:
data[i] = tmp # 把最大值
def topk(li,k):
heap = li[0:k]
for i in range((k-2)//2,-1,-1): # 前k个数建立小根堆
sift(heap,i,k-1)
for i in range(k,len(li)-1): # 第2步
if li[i] > heap[0]:
heap[0] = li[i]
sift(heap,0,k-1)
for i in range(k-1,-1,-1):
heap[0], heap[i] = heap[i],heap[0]
sift(heap,0,i-1)
return heap
import random
li = list(range(1000))
random.shuffle(li)
print(topk(li,10))
3.6 归并排序
将列表分成一个个元素,然后将两辆有序的列表归并,列表越来越大。
def merge(li,low,mid,high):
i = low
j = mid + 1
ltmp = []
while i<= mid and j<= high:
if li[i]<= li[j]:
ltmp.append(li[i])
i += 1
else:
ltmp.append(li[j])
j += 1
while i <= mid:
ltmp.append(li[i])
i += 1
while j <= high:
ltmp.append(li[j])
j += 1
li[low:high+1] = ltmp
def mergesort(li,low,high):
if low < high:
mid = (low + high)//2
mergesort(li,low,mid)
mergesort(li,mid+1,high)
merge(li,low,mid,high)
import random
li = list(range(10))
random.shuffle(li)
print(li)
mergesort(li,0,len(li)-1)
print(li)
3.7 希尔排序
希尔排序是一种分组插入的排序方法。首先取一个整数d1 = n/2,将元素分为d1个元素,每组相邻元素之间的距离是d1,在各组内进行之间插入排序。取第二个整数d2=d1/2,重负上述分组排序过程,直到d1=1,即所有元素在同一组内进行直接插入排序。
def insert_sort_gap(li,gap):
for i in range(gap,len(li)):
tmp = li[i]
j = i - gap
while j>=0 and li[j]>tmp:
li[j+gap] = li[i]
j -= gap
li[j+gap] = tmp
def shell_sort(li):
d = len(li)//2
while d >= 1:
insert_sort_gap(li,d)
d//2
import random
li = list(range(10))
random.shuffle(li)
print(li)
shell_sort(li)
print(li)
3.8 计数排序
def count_sort(li,max_count=100):
count = [0 for _ in range(max_count+1)]
for val in li:
count[val] += 1
li.clear()
for ind,val in enumerate(count):
for i in range(val):
li.append(ind)
import random
li = list(range(10))
random.shuffle(li)
print(li)
count_sort(li,max_count=100)
print(li)
3.9 基数排序
def list_to_buckets(li,base,iteration):
buckets = [[] for _ in range(base)]
for number in li:
digit = (number//(base**iteration))% base
buckets[digit].append(number)
return buckets
def buckets_to_list(buckets):
return [x for bucket in buckets for x in bucket]
def radix_sort(li,base=10):
maxval = max(li)
it=0
while base**it <= maxval:
li =buckets_to_list(list_to_buckets(li,base,iteration))
iteration += 1
return li
import random
li = list(range(10))
random.shuffle(li)
print(li)
count_sort(li,max_count=100)
print(li)
3.10 桶排序
def bucket_sort(li,n=100,max_num=10000):
buckets = [[] for _ in range(n)]
for var in li:
i = min(var // (max_num // n), n-1)
buckets[i].append(var)
for j in range(len(buckets[i]) - 1, 0, -1):
if buckets[i][j] < buckets[i][j - 1]:
buckets[i][j], buckets[i][j - 1] = buckets[i][j - 1],buckets[i][j]
else:
break
sorted_li = []
for buc in buckets:
sorted_li.extend(buc)
return sorted_li
import random
li = list(range(10))
random.shuffle(li)
print(li)
bucket_sort(li, n=100, max_num=10000)
print(li)
排序方法 | 最差 | 平均 | 最好 | 空间复杂度 | 稳定性 | 代码复杂度 |
---|---|---|---|---|---|---|
冒泡排序 | O(n^2) | O(n^2) | O(n) | O(1) | 稳定 | 简单 |
选择排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 不稳定 | 简单 |
插入排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 稳定 | 简单 |
快速排序 | O(n^2) | O(nlogn) | O(nlogn) | O(n)or O(logn) | 不稳定 | 较复杂 |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 不稳定 | 复杂 |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 | 较复杂 |
4 栈与队列
4.1 栈
栈与队列是处理数据的两种不同的线性排列方式,栈遵循先进后出的准则,队遵循数据先进先出的准则。两者个有特点。
class Stack:
def __init__(self):
self.stack = []
def push(self,element):
return self.stack.append(element)
def pop(self):
return self.stack.pop()
def get_top(self):
if len(self.stack) > 0:
return self.stack[-1]
else:
return None
def is_empty(self):
return len(self.stack) == 0
stack = Stack()
stack.push(10000)
stack.push(1000000)
stack.push(1000000000)
stack.push(10000000000000)
top = stack.get_top()
stack.pop()
stack.is_empty()
# 运用栈的特性,匹配括号
def brace_match(s):
match = {'}':'{',')':'(',']':'['}
stack = Stack()
for ch in s:
if ch in {'{','[','('}:
stack.push(ch)
else:
if stack.is_empty():
return print('stack is empty!')
elif stack.get_top() == match[ch]:
stack.pop()
else:
return print('s is not match !')
if stack.is_empty() :
return True
else:
return print('s is not correct!')
# Test1
s = ['(','[','{','}','(',')',']',')']
brace_match(s)
4.2 队列
# 环形队列:当指针 front == Maxsize+1 时,再前进一个位置就自动到0.
# 队首指针前进1: front = (front + 1) % Maxsize
# 队尾指针前进1: rear = (rear + 1) % Maxsize
# 队空条件:rear == front
# 队满条件:(rear + 1) % Maxsize == front
class Queue:
def __init__(self,size=100):
self.queue[] = [0 for _ in range(size)]
self.size = size
self.rear = 0
self.front = 0
def push(self,element):
if not self.is_filled():
self.rear = (self.rear + 1) % self.size
self.queue[self.rear] = element
else:
return IndexError('Queue is empty.')
def pop(self):
if not self.is_empty():
self.front = (self.front + 1) % self.size
return self.queue[self.front]
else:
return IndexError('Queue is empty.')
def is_empty(self):
return self.rear = self.front
def is_filled(self):
return (self.rear+1 ) % self.size == self.front
from collections import deque
q.append(1)
q.popleft()
q.appendleft(1)
q.pop()
4.3 运用栈走迷宫
maze = [
[1,1,1,1,1,1,1,1,1,1],
[1,0,0,1,0,0,0,1,0,1],
[1,0,0,1,0,0,0,1,0,1],
[1,0,0,0,0,1,1,0,0,1],
[1,0,1,1,1,0,0,0,0,1],
[1,0,0,0,1,0,0,0,0,1],
[1,0,1,0,0,0,1,0,0,1],
[1,0,1,1,1,0,1,1,0,1],
[1,1,0,0,0,0,0,0,0,1],
[1,1,1,1,1,1,1,1,1,1]
]
dirs = [
lambda x,y :(x+1,y), # 当前坐标可选择的下一步
lambda x,y :(x-1,y),
lambda x,y :(x,y-1),
lambda x,y :(x,y+1),
]
def maze_path(x1,y1,x2,y2): # 初始点(x1,y1),终点(x2,y2)
stack = [] # 建栈
stack.append((x1,y1)) # 加入起点
while(len(stack)>0):
curNode = stack[-1]
if curNode[0]==x2 and curNode[1]== y2:
for p in stack:
print(p) # 打印路径
return True
# x,y 四个方向x-1,y;x+1,y;x,y-1;x,y+1
for dir in dirs:
nextNode = dir(curNode[0],curNode[1]) #
# 如果下一个节点能走
if maze[nextNode[0]][nextNode[1]] == 0:
stack.append(nextNode)
maze[nextNode[0]][nextNode[1]] == 2 # 2 表示已经走过
break
else: # 如果没有路,标记当前点
maze[nextNode[0]][nextNode[1]] == 2
stack.pop() # 把此点踢出栈
else: # 栈不为空,但无路可走
return False
4.4 运用队走迷宫
maze = [
[1,1,1,1,1,1,1,1,1,1],
[1,0,0,1,0,0,0,1,0,1],
[1,0,0,1,0,0,0,1,0,1],
[1,0,0,0,0,1,1,0,0,1],
[1,0,1,1,1,0,0,0,0,1],
[1,0,0,0,1,0,0,0,0,1],
[1,0,1,0,0,0,1,0,0,1],
[1,0,1,1,1,0,1,1,0,1],
[1,1,0,0,0,0,0,0,0,1],
[1,1,1,1,1,1,1,1,1,1]
]
dirs = [
lambda x,y :(x+1,y),
lambda x,y :(x-1,y),
lambda x,y :(x,y-1),
lambda x,y :(x,y+1),
]
def print_r(path):
curNode = path[-1]
realpath = []
while curNode[2] != -1:
realpath.append(curNode[0:2])
curNode = path[curNode[2]]
realpath.append(curNode[0:2])
realpath.reverse()
print(realpath)
def maze_path_queue(x1,y1,x2,y2):
queue = deque()
queue.append((x1,y1,-1))
path = []
while(len(queue)>0):
curNode = queue.pop()
path.append(curNode)
if curNode[0]==x2 and curNode[1]== y2:
print_r(path)
return True
# x,y 四个方向x-1,y;x+1,y;x,y-1;x,y+1
for dir in dirs:
nextNode = dir(curNode[0],curNode[1])
# 如果下一个节点能走
if maze[nextNode[0]][nextNode[1]] == 0:
queue.append(nextNode[0]][nextNode[1],len(path)-1) # 后续节点进队
maze[nextNode[1]][nextNode[1]] == 2 # 2 表示已经走过
else:
print('没有路')
return False
5 链表
链表由一系列节点组成的元素的集合。每个节点包含两个部分,数据item和指向下一个节点的指针next。通过节点之间相互连接,最终串联一个链表。
5.1 生成链表
class Node(object):
def __init__(self,item):
self.item = item
self.next = None
a = Node(1)
b = Node(2)
c = Node(3)
a.next = b
b.next = c
print(a.next.next.next)
# 把list中的数据放在链表中
def creat_linklist(li):
head = Node(li[0])
for element in li[1:]:
node = Node(element)
node.next = head
head = node
return head
# 打印链表的内容
def print_linklist(head):
while head:
print(head.item,end=',')
head = head.next
li = [1,2,3,4,5,6]
head = creat_linklist(li)
print_linklist(head)
head
# 从头部插入数据
def creat_linklist_head(li):
head = Node(li[0])
for element in li[1:]:
node = Node(element)
node.next = head
head = node
return head
# Text
li = [1,2,3,4,5,6]
head = creat_linklist_head(li)
print_linklist(head)
head
# 从尾部插入数据
def creat_linklist_tail(li):
head = Node(li[0])
tail = head
for element in li[1:]:
node = Node(element)
tail.next = node
tail = node
return head
li = [1,2,3,4,5,6]
head = creat_linklist_tail(li)
print_linklist(head)
head
1.2.3.4.5.6.
5.2 链表节点的插入
- 先找到插入位置的前一节点为curNode
- 把要插入的节点P.next指向curNode.next: p.next = curNode.next
- 把当前节点指向: curNode.next = p
li = [1,2,3,4,5,6]
# 0 1 2 3 4 5
head = creat_linklist(li)
def linklist_insert(head,ind,data):
p = Node(data)
for i in range(ind-2):
curNode = head.next
p.next = curNode.next
curNode.next = p
return head
# Test
linklist_insert(head,3,100)
print_linklist(head)
head
# 1.2.100.3.4.5.6.
5.3 链表节点的删除
- 先找到插入位置的前一节点为curNode,curNode的下一节点为要删除的节点p=curNode.next
- curNode.next指向下一节点的下一节点: curNode.next=curNode.next.next
- del p
li = [1,2,3,4,5,6]
# 0 1 2 3 4 5
# 1 建立链表
head = creat_linklist(li)
def linklist_del(head,ind):
curNode = head
while ind>2 :
curNode = head.next
ind -= 1
p = curNode.next
curNode.next=curNode.next.next
del p
return head
linklist_del(head,3)
print_linklist(head)
head
5.4 双链表
class Node(object):
def __int__(self,item = None)
self.item = item
self.next = None
self.prior = None
双链表插入节点
'''
p.next = curNode.next
curNode.next.prior = p
p.prior = curNode
curNode.next = p
'''
双链表删除节点
'''
p = curNode.next
curNode.next = p.next
p.next.prior = curNode
del p
'''
6 二叉树
6.1 构建及遍历二叉树
class BiTreeNode:
def __init__(self,data):
self.data = data
self.lchild = None
self.rchild = None
a = BiTreeNode("A")
b = BiTreeNode('B')
c = BiTreeNode('C')
d = BiTreeNode('D')
e = BiTreeNode('E')
f = BiTreeNode('F')
g = BiTreeNode('G')
e.lchild = a
e.rchild = g
a.rchild = c
c.lchild = b
c.rchild = d
g.rchild = f
root = e
# 前序遍历
def pre_order(root):
if root:
print(root.data,end = ',')
pre_order(root.lchild)
pre_order(root.rchild)
print(pre_order(root)) # E,A,C,B,D,G,F,None
# 中序遍历
def mid_order(root):
if root:
mid_order(root.lchild)
print(root.data,end = ',')
mid_order(root.rchild)
print(mid_order(root)) # A,B,C,D,E,G,F,None
# 后序遍历
def post_order(root):
if root:
post_order(root.lchild)
post_order(root.rchild)
print(root.data,end = ',')
print(post_order(root)) # B,D,C,A,F,G,E,None
# 层次遍历
from collections import deque
def level_order(root):
queue = deque()
queue.append(root)
while len(queue) > 0 :
node = queue.popleft()
print(node.data,end = ',')
if node.lchild:
queue.append(node.lchild)
if node.rchild:
queue.append(node.rchild)
print(level_order(root)) # E,A,G,C,F,B,D,None
6.2 二叉搜索树搜索、插入、删除
二叉搜索树的节点的值大于做孩子,小于右孩子的值。
# 建立节点
class BiTreeNode():
def __init__(self,data):
self.data = data
self.lchild = None
self.rchild = None
self.parent = None
# 建立BST树
class BST:
def __init__():
self.root = None
# 1.1 用递归方法写插入函数
def insert(self,node,val):
if not node : # 如果当前节点为空
node = BiTreeNode(val)
else val < node.data:
node.lchild = self.insert(node.lchild,val)
node.lchild.parent = node
else val > node.data:
node.rchild = self.insert(node.rchild,val)
node.rchild.parent = node
return node
# 1.2 用非递归方法写插入函数,需要一个指针
def insert_no_rec(self,node,val):
p = self.root
if not p: # 如果p是空的
self.root = BiTreeNode(val)
return
while True:
if not node : # 如果当前节点为空
node = BiTreeNode(val)
else val < node.data:
node.lchild = self.insert(node.lchild,val)
node.lchild.parent = node
else val > node.data:
node.rchild = self.insert(node.rchild,val)
node.rchild.parent = node
return node