可以看这个特别好的:《数据结构与算法基础》教学视频目录
1.单向链表
单链表节点一个类,单链表一个类,单链表这个类中的元素都是节点类的。
单链表的操作:
is_empty() 链表是否为空
取cur指向链表的首节点,判断是不是Nonelength() 链表长度
取cur指向链表的首节点,设置count用来计数,当前指向若不为None,则计数+1,移动cur指向下一个节点。travel() 遍历整个链表
思路同链表长度,把计数的部分换成print-
first_add(item) 链表头部添加元素
新添加的元素需要定义成节点类的一个实例。新添加的元素就是头节点,新添加节点的下一个指向原链表的头节点。把链表的头节点指向新添加的节点。
last_add(item) 链表尾部添加元素
新添加的元素需要定义成节点类的一个实例。
判断当前链表是不是为空,如果是空,那这个链表的头节点指向新添加的节点。
如果不是空,那就将cur指向头节点,按照travel()的思路遍历这个链表,一直走到最后一个节点,将最后一个节点的下一个元素设置为新添加的这个节点。注意判断是否最后一个节点的条件。-
insert(pos, item) 指定位置添加元素
新添加的元素需要定义成节点类的一个实例。
判断pos的值,如果pos<=0,则相当于在首部插入,调用首部插入的方法。
如果pos的值>链表长度-1(注意这里pos的值是从0开始的),则相当于尾部插入,调用尾部插入的方法。
否则:定义pre指向当前链表的头节点,用一个count计算当前指向的索引值,移动cur直到走到pos-1的位置,将新节点的下一个元素指向原链表这个位置的下一个元素,原链表的下一个位置指向新节点。见下图
-
remove(item) 删除节点
定义两个指针,pre一开始是None,cur 指向原链表的头节点。如果删除的是头节点,那么把链表的头节点指向原链表头节点的下一个节点。如果不是头节点,就将pre指向当前cur的节点,cur的指向一个一个的往后挪,(这样就相当于pre在后,cur在前)直到cur指向的值就是我们要删除的值,把pre的下一个节点指向cur的下一个节点。
search(item) 查找节点是否存在
思路同travel()
class Node(object):
def __init__(self,item):
self.item = item
self._next = None
class singleLinkList(object):
def __init__(self):
# 头节点
self._head = None
def is_empty(self):
return self._head == None
def length(self):
# 首先要指向头节点
cur = self._head
count = 0
while cur is not None:
count = count+1
cur = cur._next
return count
def travel(self):
if self.is_empty():
return
# 首先要指向头节点
cur = self._head
while cur != None:
print(cur.item)
cur = cur._next
print("")
def first_add(self,elem):
# 把新添加的数定义到Node类里,这样它就会有item和_next两个属性
p = Node(elem)
p._next = self._head
# 把链表的头节点指向新添加的节点
self._head = p
def last_add(self,elem):
cur = self._head
p = Node(elem)
# 首先判断这个链表是不是空的
if self.is_empty():
self._head = p
else:
cur = self._head
while cur._next != None:
cur = cur._next
cur._next = p
def insert(self,index,elem):
# 如果插入的地方在第一个位置之前,直接调用在首部插入的方法
if index<=0 :
self.first_add(elem)
# 如果插入的地方在最后一个位置之后,直接调用在尾部插入的方法
elif index>(self.length()-1):
self.last_add(elem)
else:
p = Node(elem)
count = 0
pre = self._head
while count<(index-1):
pre = pre._next
count = count+1
p._next = pre._next
pre._next = p
def remove(self,elem):
cur = self._head
pre = None
while cur is not None:
if cur.item == elem:
# 如果第一个就是要删除的节点
if not pre:
self._head = cur._next
else:
pre._next = cur._next
break
else:
# 将删除位置前一个节点的next指向删除位置的后一个节点
pre = cur
cur = cur._next
def search(self,elem):
cur = self._head
index = 0
while cur is not None:
if cur.item == elem:
return index
else:
cur = cur._next
index = index+1
return index
if __name__ == "__main__":
ll = singleLinkList()
print("-------创建链表,首部加1,首部加2---------")
ll.first_add(1)
# print(ll.travel())
ll.first_add(2)
ll.travel()
print("-------尾部添加3---------")
ll.last_add(3)
ll.travel()
print("-------在索引为2的位置添加4---------")
ll.insert(2, 4)
print("length:",ll.length())
ll.travel()
print("-------查找链表中某一个数的索引---------")
index = ll.search(2)
print(index)
print("-------删除链表中某一个数---------")
ll.remove(3)
# print "length:",ll.length()
ll.travel()
2.循环单链表
-
首部添加元素
首先判断这个链表是不是空,如果是空,把头指针指向新添加的这个p节点,并且p节点的下一节点指向头节点。(因为是循环的)
如果不是空,那么先把p节点的下一个节点指向头节点,然后往后走,找到最后一个节点,把原链表的最后一个节点的下一个节点指向新加入的p节点,并且把头指针指向这个p节点(因为是首部加入) - 尾部添加元素
首先判断原链表是不是空的,如果是空的,就按照上面的,把头指针(self._head)指向新添加的这个p节点,并且p节点的下一节点指向头节点。
如果不是空,就找到原链表的最后一个节点,将最后一个节点的下一节点指向p节点,然后p节点的下一个节点指向头节点,头指针(self._head)没变。 - remove方法还需要好好看看。
class Node(object):
def __init__(self,item):
self.item = item
self._next = None
class circularLinkList(object):
def __init__(self):
# 头节点
self._head = None
def is_empty(self):
return self._head == None
def length(self):
# 首先要指向头节点
cur = self._head
count = 0
while cur is not None:
count = count+1
cur = cur._next
return count
def travel(self):
if self.is_empty():
return
# 首先要指向头节点
cur = self._head
while cur._next != self._head:
print(cur.item)
cur = cur._next
print(cur.item)
print("")
def first_add(self,elem):
# 把新添加的数定义到Node类里,这样它就会有item和_next两个属性
p = Node(elem)
if self.is_empty():
self._head = p
p._next = self._head
else:
# 首先将新加入的这个节点的下一个节点指向首节点
p._next = self._head
# 然后将原链表的尾节点指向这个新加入的节点
cur = self._head
while cur._next != self._head:
cur = cur._next
cur._next = p
self._head = p
def last_add(self,elem):
p = Node(elem)
# 首先判断这个链表是不是空的
if self.is_empty():
self._head = p
p._next = self._head
else:
# 直接走到最后的节点
cur = self._head
while cur._next != self._head:
cur = cur._next
cur._next = p
p._next = self._head
def insert(self,index,elem):
# 如果插入的地方在第一个位置之前,直接调用在首部插入的方法
if index<=0 :
self.first_add(elem)
# 如果插入的地方在最后一个位置之后,直接调用在尾部插入的方法
elif index>(self.length()-1):
self.last_add(elem)
else:
p = Node(elem)
count = 0
pre = self._head
while count<(index-1):
pre = pre._next
count = count+1
p._next = pre._next
pre._next = p
def remove(self,elem):
# 如果原链表为空
if self.is_empty():
return
cur = self._head
pre = None
# 如果链表的第一个元素就是要删除的元素
if cur.item == elem:
# 如果链表多于一个节点,就找到最后一个节点
if cur._next != self._head:
while cur._next != self._head:
cur = cur._next
cur._next = self._head._next
self._head = self._head._next
else:
# 如果链表只有一个元素,就直接置空
self._head = None
else:
pre = self._head
while cur._next != self._head:
# 如果找到了要找的元素
if cur.item == elem:
pre._next = cur._next
return
else:
pre = cur
cur = cur._next
# cur指向了尾节点
if cur.item == elem:
pre._next = self._head
def search(self,elem):
if self.is_empty():
return False
cur = self._head
index = 0
while cur._next != self._head:
if cur.item == elem:
return index
else:
cur = cur._next
index = index+1
if cur.item == elem:
return index
else:
return False
if __name__ == "__main__":
ll = circularLinkList()
# print(ll.length())
ll.travel()
print("--------首部添加----------")
ll.first_add(2)
ll.first_add(1)
ll.first_add(99)
ll.travel()
print("--------尾部添加----------")
ll.last_add(6)
ll.travel()
print("--------尾部添加----------")
ll.last_add(5)
ll.travel()
print("--------移除元素----------")
# ll.remove(5)
# ll.travel()
print("--------查找元素----------")
print(ll.search(6))
3.栈
栈是一种特殊的线性表,仅能在线性表的一端操作,栈顶允许操作,栈底不允许操作。
栈的特性:后进先出
class Stack(object):
def __init__(self):
self.items = []
def is_empty(self):
# 判断栈是否为空
return self.items == []
def push(self,elem):
# 添加元素到栈
self.items.append(elem)
def pop(self):
# 弹出元素
return self.items.pop()
def peek(self):
# 返回栈顶的元素
return self.items[len(self.items)-1]
def size(self):
return len(self.items)
def travel(self):
for i in range(self.size()):
print(self.items[i],end=" ")
if __name__ == '__main__':
stack = Stack()
print(stack.travel())
print("======判断栈是否为空==========")
print(stack.is_empty())
print("======栈顶加入元素==========")
stack.push(2)
stack.push(3)
stack.push(12)
stack.push(4)
print(stack.travel())
print("栈的长度为:", stack.size())
print("======判断栈是否为空==========")
print(stack.is_empty())
print("======弹出栈顶元素==========")
print(stack.pop())
print("======返回栈顶元素==========")
print(stack.peek())
print("栈的长度为:",stack.size())
4.队列
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作。
特点:先进先出
class Queue(object):
def __init__(self):
self.items =[]
def is_empty(self):
return self.items == []
def enqueue(self,elem):
# 队列添加元素
self.items.insert(0,elem)
def dequeue(self):
# 从队列头部删除一个元素
return self.items.pop()
def size(self):
return len(self.items)
if __name__ == '__main__':
queue = Queue()
print("======判断队列是否为空==========")
print(queue.is_empty())
print("======队列加入元素==========")
queue.enqueue(2)
queue.enqueue("hello")
queue.enqueue("end")
print("队列的长度为:", queue.size())
print("======判断队列是否为空==========")
print(queue.is_empty())
print("======返回队列头部元素==========")
print(queue.dequeue())
print("队列的长度为:",queue.size())
双端队列
特点:双端队列可以在队列任意一端入队和出队。
class DoubleQueue(object):
"""
Deque() 创建一个空的双端队列
add_front(item) 从队头加入一个item元素
add_rear(item) 从队尾加入一个item元素
remove_front() 从队头删除一个item元素
remove_rear() 从队尾删除一个item元素
is_empty() 判断双端队列是否为空
size() 返回队列的大小
"""
def __init__(self):
self.items = []
def is_empty(self):
return self.items == []
def add_front(self,elem):
# 从队头加入一个元素
self.items.insert(0,elem)
def add_rear(self,elem):
# 队尾加入一个元素
self.items.append(elem)
def remove_front(self):
# 队头删除一个元素
return self.items.pop(0)
def remove_rear(self):
# 队尾删除一个元素
return self.items.pop()
def size(self):
return len(self.items)
def travel(self):
for i in range(self.size()):
print(self.items[i],end=" ")
if __name__ == '__main__':
deque = DoubleQueue()
print("======判断双端队列是否为空==========")
print(deque.is_empty())
deque.add_front(1)
deque.add_front(2)
deque.add_rear(3)
deque.add_rear(4)
print("======双端队列的值为:==========")
deque.travel()
print("双端队列的长度为:",deque.size())
print("======队头删除的元素==========")
print(deque.remove_front())
print("======队尾删除的元素==========")
print(deque.remove_rear())
print("双端队列的长度为:", deque.size())
4.树
二叉树
对于任意一棵二叉树,如果其叶结点数为N0,而度数为2的结点总数为N2,则N0=N2+1;
证明需要知道:二叉树(非空)的结点数等于边数+1(可以理解为,除了根节点,每一个节点对应一条边),可以设度数为0的节点为N0,度数为1的节点N1,度数为2的节点N2,有N0+N1+N2(节点数)= 1+N1+2N2(边数),度数为1的节点对对应一个边,度数为2的节点对应两个边。
-
满二叉树:一棵深度为k且有2的k次方减1个结点的二叉树是满二叉树。除叶子结点外的所有结点均有两个子结点。节点数达到最大值。所有叶子结点必须在同一层上。
-
完全二叉树:若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。
二者的关系:满二叉树肯定是完全二叉树,完全二叉树不一定是满二叉树。
- 平衡二叉树(AVL树):当且仅当任何节点的两棵子树的高度差不大于1的二叉树;
- 排序二叉树(二叉查找树(英语:Binary Search Tree),也称二叉搜索树、有序二叉树);
树的前序遍历:中左右
中序遍历:左中右
后续遍历:左右中
5. 图 G=(V,E)
相关概念
图的组成:顶点V和边E
分类:有向图和无向图
顶点的度:对于无向图来说,一个顶点的度就是连接该顶点的边的数量,记做D(V);对于有向图来说,分为入度ID(V)(以该顶点为端点的入边数量)和出度OD(V)(以该顶点为端点的出边数量),D(V)=ID(V)+OD(V)
邻接顶点:对于无向图来说,就是一条边的两个顶点;对于有向图来说,两个顶点分别称为起始顶点(入边邻接顶点)和结束顶点(出边邻接顶点)
无向完全图:M个顶点的无向完全图总边数为M(M-1)/2
有向完全图:N个顶点的有向完全图,总边数为N(N-1)
有向无环图(DAG图):一个有向图从一个顶点出发经过若干条边无法回到该顶点。
网(即有权图)
图的存储结构
图的存储结构:邻接矩阵表示法、邻接表表示法
1⃣️邻接矩阵表示法:就是用两个数组表示图。缺点是n个顶点的图需要n*n个存储空间,当图为稀疏图时会造成空间浪费。
顶点表记录顶点信息,邻接矩阵表示顶点之间的关系。
有向图邻接矩阵中第i行表示以结点vi为尾的弧(即出度边[发出的弧]),第j列表示的是以结点vj为头的弧(即入度边),顶点的出度=第i行元素之和,顶点的入度=第j列元素之和
2⃣️邻接表表示法:(多重链表链式存储结构)
头结点:一维数组,其中有两个元素,第一个元素是顶点的数据元素本身,第二个元素是指针,指向边结点的地址。
表结点:第一个元素表示邻接的顶点是顶点表中的哪个元素,第二个指针指向下一个边。
对于一个顶点来说,有几个边就记录几个边结点
如果是网,可以在表结点中增加一个域表示权值。
特点:
邻接表不唯一;若无向图中有n个顶点e条边,则邻接表需要n个头结点和2e个表结点,适合存储稀疏图;有向图中有n个顶点e条边,则邻接表需要n个头结点和e个表结点
怎么看度?有几个表结点就是有几个度。
3.排序
冒泡排序(O(n^2))
思路:比较前后两个数的大小,从前到后比较到最后,这样第一次冒泡完最后一个数是整个里面最大的数,然后进行第二次冒泡...假设我们要排序的有m个数,那么就需要m-1次冒泡。
def BubbleSort(myList):
length = len(myList)
for i in range(length-1):
#注意这个地方,因为还有一个j+1,所以j的取值是length-i-1
for j in range(length-i-1):
if myList[j]>myList[j+1]:
myList[j],myList[j+1] = myList[j+1],myList[j]
print(myList)
BubbleSort([49,38,65,97,76,13,27])
选择排序(O(n^2))
思路:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
注:选择排序每一趟只交换一个数!!所以在每一趟比较的时候记录下来最小的数的索引值,在未排序序列都遍历过一边之后,把最小的值和第一个为止的进行交换。
def Selection_sort(myList):
length = len(myList)
# 总共需要length-1次,因为第length次前面的都定了
for i in range(length-1):
min_index = i
for j in range(i,length):
if myList[j]
插入排序(O(n^2))
思路:未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
def insert_sort(myList):
length = len(myList)
for i in range(1,length):
for j in range(i,0,-1):
if myList[j]0 and myList[index-1]>temp:
myList[index]=myList[index-1]
index = index-1
myList[index] = temp
insert_sort([49,38,65,97,76,13,27,49])
快速排序(O(nlogn))
思想:
算法-快速排序-python3实现
讲的很好的视频
def QuickSort(myList,start,end):
i,j = start,end
base = myList[i]
while i=base:
j = j-1
myList[i]=myList[j]
while i
快速排序2:
def quick_sort(self,lst):
if not lst:
return []
pivot = lst[0]
left = self.quick_sort([x for x in lst[1: ] if x < pivot])
right = self.quick_sort([x for x in lst[1: ] if x >= pivot])
return left + [pivot] + right
归并排序:
实质其实就是合并两个有序的列表
def merge_sort(collection):
if len(collection)<=1:
return collection
mid = len(collection) // 2
return merge(merge_sort(collection[:mid]), merge_sort(collection[mid:]))
def merge(left,right):
result=[]
while left and right:
result.append(left.pop(0) if left[0]<=right[0] else right.pop(0))
return result+left+right
print(merge_sort([2,-1,0,3,1,9]))
堆排序:
1⃣️Python实现:
堆排序的Python实现(附详细过程图和讲解)
主要的思想是先构建一个大根堆,然后让大根堆的顶点与堆右下角的元素交换,再构建成大根堆。
堆,其实就是一个完全二叉树,特点是根节点的值最大(大根堆)或者最小(小根堆)
2⃣️Python调用heaq库 (参考这篇文章Python标准库模块之heapq)
# 使用heaq库构建堆
import heapq
nums = [2, 3, 5, 1, 54, 23, 132]
heap = []
for num in nums:
heapq.heappush(heap, num)
#heapq.heappop() 函数弹出堆中最小值
print([heapq.heappop(heap) for _ in range(len(nums))])
#获取堆中的最大或最小的范围值
print(heapq.nlargest(3, nums))
print(heapq.nsmallest(3, nums))
4.常见算法
穷举法
贪心法:典型例子是最大连续子数组和(不太确定下面这个是不是用贪心法做的)
class Solution:
def FindGreatestSumOfSubArray(self, array):
# write code here
if not array:
return 0
cur_sum = 0
max_sum = array[0]
for i in range(len(array)):
if cur_sum <= 0:
cur_sum = array[i]
else:
cur_sum += array[i]
if cur_sum > max_sum:
max_sum = cur_sum
return max_sum
分治法 :典型例子有快排
回溯法:例子有那个左上到右下的棋盘
动态规划 :