数据结构与算法
1.什么是数据结构
-
定义:
-
“数据结构是数据对象,以及存在于该对象的实例和组成实例的数据元素之间的各种联系。这些联系可以 通过定义相关的函数来给出。” --- 《数据结构、算法与应用》
-
“数据结构(data structure)是计算机中存储、组织数据的方式。通常情况下,精心选择的数据结构可以 带来最优效率的算法。” ---中文维基百科
-
“数据结构是ADT(抽象数据类型 Abstract Data Type)的物理实现。” --- 《数据结构与算法分析》
什么是抽象数据类型 ADT?
ADT: Abstract Data Type,抽象数据类型,我们在组合已有的数据结构来实现一种新的数据类型, ADT 定义了类型的数据和操作。
我们以抽象一个背包(Bag) 数据类型来说明,背包是一种容器类型,我们可以给它添加东西,也可以移除东西,并且我们想知道背包里有多少东西。于是我们可以定义一个新的数据类型叫做 Bag,在类中实现背包的所有功能.
-
class Bag: """ 背包类型 """ pass
-
2.常见的数据结构
栈、队列、链表、集合、字典、树.........
注意: 数据结构和语言无关, 基本常见的编程语言都有直接或者间接的使用上述常见的数据结构
列表
列表的方法:
序号 | 方法 |
---|---|
1 | list.append(obj)在列表末尾添加新的对象 |
2 | list.count(obj)统计某个元素在列表中出现的次数 |
3 | list.extend(seq)在列表末尾一次性追加另一个序列中的多个值(用新列表扩展原来的列表) |
4 | list.index(obj)从列表中找出某个值第一个匹配项的索引位置 |
5 | list.insert(index, obj)将对象插入列表 |
6 | list.pop(index=-1)移除列表中的一个元素(默认最后一个元素),并且返回该元素的值 |
7 | list.remove(obj)移除列表中某个值的第一个匹配项 |
8 | list.reverse()反向列表中元素 |
9 | list.sort( key=None, reverse=False)对原列表进行排序 |
10 | list.clear()清空列表 |
11 | list.copy()复制列表 |
字典
字典的方法:
序号 | 函数及描述 |
---|---|
1 | dict.clear()删除字典内所有元素 |
2 | dict.copy()返回一个字典的浅复制 |
3 | dict.fromkeys()创建一个新字典,以序列seq中元素做字典的键,val为字典所有键对应的初始值 |
4 | dict.get(key, default=None)返回指定键的值,如果键不在字典中返回 default 设置的默认值 |
5 | key in dict如果键在字典dict里返回true,否则返回false |
6 | dict.items()以列表返回一个视图对象 |
7 | dict.keys()返回一个视图对象 |
8 | dict.setdefault(key, default=None)和get()类似, 但如果键不存在于字典中,将会添加键并将值设为default |
9 | dict.update(dict2)把字典dict2的键/值对更新到dict里 |
10 | dict.values()返回一个视图对象 |
11 | pop(key,default)删除字典 key(键)所对应的值,返回被删除的值。 |
12 | popitem()返回并删除字典中的最后一对键和值。 |
集合
集合的方法:
方法 | 描述 |
---|---|
add() | 为集合添加元素 |
clear() | 移除集合中的所有元素 |
copy() | 拷贝一个集合 |
difference() | 返回多个集合的差集 |
difference_update() | 移除集合中的元素,该元素在指定的集合也存在。 |
discard() | 删除集合中指定的元素 |
intersection() | 返回集合的交集 |
intersection_update() | 返回集合的交集。 |
isdisjoint() | 判断两个集合是否包含相同的元素,如果没有返回 True,否则返回 False。 |
issubset() | 判断指定集合是否为该方法参数集合的子集。 |
issuperset() | 判断该方法的参数集合是否为指定集合的子集 |
pop() | 随机移除元素 |
remove() | 移除指定元素 |
symmetric_difference() | 返回两个集合中不重复的元素集合。 |
symmetric_difference_update() | 移除当前集合中在另外一个指定集合相同的元素,并将另外一个指定集合中不同的元素插入到当前集合中。 |
union() | 返回两个集合的并集 |
update() | 给集合添加元素 |
len() | 计算集合元素个数 |
数组
数组是最常用到的一种线性结构,其实 python 内置了一个 array 模块,但是大部人甚至从来没用过它。 Python 的 array 是内存连续、存储的都是同一数据类型的结构,而且只能存数值和字符。
数组提供了高效的、固定类型的一维数组。相比于列表,array
对象节省了空间且在处理大量同类型数据时速度更快。要使用 array
模块,你需要首先导入它,然后通过 array.array()
函数来创建数组。
注意:array模块不支持多维数组,只有一维数组功能
数组的方法:
方法 | 描述 |
---|---|
append() | 在数组末尾添加一个新项 |
buffer_info() | 返回给出当前内存信息的信息 |
byteswap() | 对数组的所有项进行Byteswap |
count() | 返回对象出现的次数 |
extend() | 通过从可迭代对象中附加多个元素来扩展数组 |
fromfile() | 从文件对象中读取项 |
fromlist() | 从列表中追加项目 |
frombytes() | 从字符串中附加项 |
index() | 返回对象第一次出现的索引 |
insert() | 在数组中指定的位置插入一个新项 |
pop() | 删除并返回项目(默认最后一个) |
remove() | 删除对象的第一次出现 |
reverse() | 反转数组中元素的顺序 |
tofile() | 将所有项写入一个文件对象 |
tolist() | 返回转换为普通列表的数组 |
tobytes() | 返回转换为字符串的数组 |
栈
栈(stack),它是一种运算受限的线性表,后进先出(LIFO)
LIFO(last in first out)表示就是后进入的元素, 第一个弹出栈空间.
生活中,栈的思想类似于在一个箱子里放若干本书,之后取书的时候,最先取出的是最后一个放的一本。
栈常见的操作
-
push(element)
: 添加一个新元素到栈顶位置. -
pop()
:移除栈顶的元素,同时返回被移除的元素。 -
peek()
:返回栈顶的元素,不对栈做任何修改(这个方法不会移除栈顶的元素,仅仅返回它)。 -
isEmpty()
:如果栈里没有任何元素就返回true
,否则返回false
。 -
clear()
:移除栈里的所有元素。 -
size()
:返回栈里的元素个数。这个方法和数组的length
属性很类似。
实现:
class Stack:
def __init__(self):
self.items = [] # 栈中的属性
# 入栈
def push(self,el):
self.items.append(el)
# 出栈
def pop(self):
return self.items.pop()
# 查看栈顶元素
def peek(self):
return self.items[-1]
# 判断栈是否为空
def is_empty(self):
return self.items == 0
# 清空栈
def clear(self):
self.items.clear()
# 获取栈中元素的个数
def count(self):
return len(self.items)
s1 = Stack()
s1.push(1)
s1.push(2)
print(s1.items) # [1, 2]
print(s1.peek()) # 2
print(s1.is_empty()) # False
print(s1.count()) # 2
队列
队列(Queue),它是一种运算受限的线性表,先进先出(FIFO First In First Out)
-
队列是一种受限的线性结构
-
受限之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作。
-
生活中类似的队列结构:优先排队的人, 优先处理. (买票, 结账, WC)
-
队列常见的操作:
-
enqueue(element)
:向队列尾部添加一个(或多个)新的项。 -
dequeue()
:移除队列的第一(即排在队列最前面的)项,并返回被移除的元素。 -
front()
:返回当前队列中第一个元素,也将是最先被移除的元素。队列不做任何变动(不移除元素,只返回元素信息——与Stack
类的peek
方法非常类似)。 -
isEmpty()
:如果队列中不包含任何元素,返回true
,否则返回false
。 -
size()
:返回队列包含的元素个数,与数组的length
属性类似.
-
1.普通的队列实现
class Queue:
def __init__(self):
self.items = []
self.size = 0
# 入队
def enqueue(self,el):
self.items.append(el)
self.size+=1
# 判断队是否为空
def is_empty(self):
return self.size == 0
# 出队
def dequeue(self):
if self.is_empty():
return None
else:
return self.items.pop(0)
self.size-=1
#返回队列中的第一个元素
def one_el(self):
if self.is_empty():
return None
else:
return self.items[0]
#返回队列中的元素个数
def count(self):
return len(self.items)
q1 = Queue()
q1.enqueue(5)
q1.enqueue(6)
q1.enqueue(7)
# print(q1.items) # [5, 6, 7]
q1.dequeue() # [6,7]
# print(q1.items)
q1.is_empty()
# print(q1.items) # [6,7]
print(q1.count()) # 2
2.优先级队列的介绍和实现
优先级队列, 在插入一个元素的时候会考虑该数据的优先级.(和其他数据优先级进行比较) ,比较完成后, 可以得出这个元素正确的队列中的位置. 其他处理方式, 和队列的处理方式一样
计算机中, 我们也可以通过优先级队列来重新排序队列中任务的顺序,比如每个线程处理的任务重要性不同, 我们可以通过优先级的大小, 来决定该线程在队列中被处理的次序.
实现:
class Node:
def __init__(self, el, rank):
self.el = el
self.rank = rank
#优先级队列
class Queue2:
def __init__(self):
self.items = []
self.size = 0
def enqueue(self,el,rank):
n1=Node(el,rank)
flag=True
for index in range(len(self.items)):
# 找到第一个优先级n1级别低的,插在它的前面位置
if n1.rank < self.items[index].rank:
self.items.insert(index,n1)
flag=False
break
if flag:
self.items.append(n1)
self.size+=1
def is_empty(self):
return self.size==0
def dequeue(self):
if self.is_empty():
return None
else:
self.size-=1
return self.items.pop(0)
q2=Queue2()
q2.enqueue("HQ",1)
q2.enqueue("HQ2",2)
q2.enqueue("HQ2",2)
q2.enqueue("HQ2",3)
print(q2.dequeue().el) # HQ
链表
-
链表是链式的存储多个元素.
-
但不同于列表, 链表中的元素在内存中不必是连续的空间
-
链表的每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(有些语言称为指针或者链接)组成
-
链表访问任何一个位置的元素时, 都需要从头开始访问.(无法跳过第一个元素访问任何一个元素)
-
-
链表常见的操作
-
append(element)
:向列表尾部添加一个新的项 -
insert(position, element)
:向列表的特定位置插入一个新的项。 -
remove(element)
:从列表中移除一项。 -
indexOf(element)
:返回元素在链表中的索引。如果列表中没有该元素则返回-1
。 -
removeAt(position)
:从列表的特定位置移除一项。 -
isEmpty()
:如果链表中不包含任何元素,返回true
,如果链表长度大于0则返回false
。 -
size()
:返回链表包含的元素个数。与数组的length
属性类似。
-
1.单向链表的实现
class Node:
def __init__(self, data):
self.data = data
self.next = None
class LinkedList:
def __init__(self):
self.head = None
def append(self, element):
if not self.head:
self.head = Node(element)
else:
current = self.head
while current.next:
current = current.next
current.next = Node(element)
def remove(self, element):
current = self.head
# 如果头节点就是要删除的节点
if current and current.data == element:
self.head = current.next
current = None
return
# 查找要删除的节点
prev = None
while current and current.data != element:
prev = current
current = current.next
# 如果节点不存在
if not current:
return
# 从链表中移除节点
prev.next = current.next
current = None
def indexOf(self, element):
current = self.head
index = 0
while current:
if current.data == element:
return index
current = current.next
index += 1
return -1
def removeAt(self, position):
if position < 0 or not self.head:
return
current = self.head
index = 0
# 如果要删除的是头节点
if position == 0:
self.head = current.next
current = None
return
# 查找要删除的节点的前一个节点
prev = None
while current and index < position:
prev = current
current = current.next
index += 1
# 如果位置无效
if index != position or not current:
return
# 从链表中移除节点
prev.next = current.next
current = None
def isEmpty(self):
return self.head is None
def size(self):
current = self.head
count = 0
while current:
count += 1
current = current.next
return count
# 使用示例
ll = LinkedList()
ll.append(1)
ll.append(2)
ll.append(3)
print(ll.indexOf(2)) # 输出: 1
ll.remove(2)
print(ll.size()) # 输出: 2
ll.removeAt(0)
print(ll.isEmpty()) # 输出: False(如果链表中还有其他元素)