文章目录
0 引言
想成为一名合格的算法工程师,就要努力学习好数据结构,此乃基础,要牢固掌握(共勉)。数据结构是计算机存储、组织数据的方式。主要研究数据的逻辑结构、物理结构和存储结构。分为线性结构和非线性结构。但这些都是概念性的理论,大部分学习方式是依据常用的八大数据结构(数组,链表,树,队列,栈,堆,散列表,图)来进一步细化学习。
当然,每一种数据结构都有独特的数据存储方式,各有优缺点,设计算法时,结合常用的检索,插入,删除,更新和排序等运算,尽量实现在空间复杂度和时间复杂度两方面的最优化。
我个人学习的话,也配合刷题(牛客网,Leetcode);
再配合动态可视化算法网站(https://visualgo.net/zh)‘食用’更佳;
此外,推荐一个Github 50+k star的大佬学习算法的技巧和心得汇总;
最后,就是多刷,多查,多总结,加油!
1 数组(Array)
简单来说,数组就是有序的元素序列。既然其中的元素有序,就能通过索引(下标)快速查询元素,注意数组下标一般是从0开始,常见的数组有一维数组,二维数组,多维数组。
Python语言中列表(list)很类似数组,简单创建一下数组:
# 一维数组
list1 = [0, 1, 2]
# 二维数组
list2 = [[0,1,2],[3,4,5],[7,8,9]]
# 依次类推
数组的特点:
- 数组是相同的数据类型的元素的集合,但python中的列表可以不同数据类型;
- 数组中的各元素存储是有先后顺序的,因此很适合频繁检索,快速查找;
- 数组需要提前申请空间,故空间复杂度大,随机访问效率高,故时间复杂度小;
数组的优点:
- 按照索引查询元素速度快;
- 能存储大量数据;
- 按照索引遍历数组方便。
数组的缺点:
- 按照内容查找元素速度慢;
- 数组的大小一经确定不能改变;
- 数组只能存储一种类型的数据;
- 增加、删除元素效率慢
2 链表(Linked List)
记得之前在学C语言时,看到这样说:链表相对于数组更好理解,因为数组是最基本的数据结构,提前设定空间大小,虽然查询较快,但插入元素和删除元素比较麻烦,为了弥补这些缺点,链表应用而生,链表是一种物理存储单元上非连续、非顺序的存储结构,简单来说,链表的空间大小不需要提前设定,是动态扩展的,通过指针可以在任意位置插入和删除元素,效率很高,时间复杂度小;但相应的由于动态的特点,链表的查询比较麻烦,时间复杂度大。
此外,链表主要分为单链表,双向链表和循环链表;
Python语言实现单链表类及一些插入,删除等操作;
class SingleLinkedList(object):
"""单链表类"""
def __init__(self):
self.head = None
def is_empty(self):
return self.head is None
def add(self, newdata):
node = SingleListNode(newdata, _next=self.head)
self.head = node
def append(self, newdata):
node = SingleListNode(newdata)
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, newdata):
"""将newdata插入pos位置之后"""
if pos <= 0:
self.add(newdata)
elif pos > self.length() - 1:
self.append(newdata)
else:
node = SingleListNode(newdata)
cur = self.head
count = 0
while count < pos - 1:
count += 1
cur = cur.next
node.next = cur.next
cur.next = node
def remove(self, olddata):
"""从单链表中删除所有的olddata"""
cur = self.head
pre = None
while cur is not None:
if cur.item == olddata:
if not pre:
self.head = cur.next
else:
pre.next = cur.next
cur = cur.next
else:
pre = cur
cur = cur.next
def length(self):
"""返回单链表的长度"""
cur = self.head
count = 0
while cur is not None:
count += 1
cur = cur.next
return count
def travel(self):
"""打印整个单链表
return
------
ls: list,从前至后的单链表"""
cur = self.head
ls = []
while cur is not None:
ls.append(cur.item)
cur = cur.next
return ls
def search(self, data):
cur = self.head
while cur is not None:
if cur.item == data:
return True
else:
cur = cur.next
return False
链表的优点:
- 任意位置插入或删除元素的速度快,时间复杂度 o ( 1 ) o(1) o(1);
- 内存利用率高,不会浪费内存;
- 链表的空间大小不固定,可以动态扩展;
链表的缺点:
- 随机访问效率低,时间复杂度 o ( n ) o(n) o(n)
总之,和数组的对比:
- 如果想要快速访问数据,不经常有插入和删除元素,选择数组;
- 如果需要经常的插入和删除元素,而对访问元素时的效率没有很高要求的话,选择链表。
3 树(Tree)
树状图是一种数据结构,由 n ( n > 1 ) n(n>1) n(n>1)个有限结点组成一个具有层次关系的集合,并有以下特点:
- 每个结点有零个或多个子结点;
- 没有父结点的结点称为根结点;
- 每一个非根结点有且只有一个父结点;
- 除了根结点外,每个子结点可以分为多个不相交的子树;
树的分类:
- 无序树:树中任意节点的子结点之间没有顺序关系,这种树称为无序树,也称为自由树;
- 有序树:树中任意节点的子结点之间有顺序关系,这种树称为有序树;
- 二叉树:每个节点最多含有两个子树的树称为二叉树;
- 满二叉树:叶节点除外的所有节点均含有两个子树的树被称为满二叉树;
- 完全二叉树:有 2 k − 1 2^k-1 2k−1 个节点的满二叉树称为完全二叉树;
- 哈夫曼树(最优二叉树):带权路径最短的二叉树称为哈夫曼树或最优二叉树;
而在算法设计中,用的比较多的就是树的遍历,就是常见的:先序遍历,中序遍历,后序遍历,层次遍历。
依据下图:
- 先序遍历:ABDECF(根-左-右)
- 中序遍历:DBEAFC(左-根-右)(注:仅二叉树有中序遍历)
- 后序遍历:DEBFCA(左-右-根)
- 层次遍历:ABCDEF(同广度优先搜索)
Python算法实现:https://blog.csdn.net/MRZHUGH/article/details/107888908
4 队列(Queue)
5 栈(Stack)
栈是允许在同一端进行插入和删除操作的特殊线性表。其中,允许进行插入和删除操作的一端称为栈顶(top),另一端为栈底(bottom);栈底固定,而栈顶浮动;栈中元素个数为零时称为空栈。插入一般称为进栈(Push),删除则称为退栈(Pop)。栈也称为先进后出表(First In Last Out, FILO)。
Python实现栈类:
"""
栈类:
栈是有序的LIFO(后进先出);
1.Stack()创建新的栈;
2.push(item)添加新的栈元素到顶部;
3.pop()删除栈顶部并返回栈顶的值,栈被修改;
4.peek()返回栈顶值,不修改栈;
5.is_empty()测试栈是否为空,返回布尔值(True,False);
6.size()返回栈长度;
"""
class Stack(object):
# 初始化
def __init__(self):
self.list = []
# push操作
def push(self, item):
# 添加一个新元素到item到栈顶
self.list.append(item)
# pop操作
def pop(self):
# 弹出栈顶元素
return self.list.pop()
# 返回栈顶元素
def peek(self):
print("栈顶元素是:")
if self.list:
return self.list[-1]
else:
return None
# 判断栈是否为空
def is_empty(self):
return self.list == []
# 返回栈的元素个数
def size(self):
print("栈的大小:")
return len(self.list)
实例化:
# 实例化栈并push操作元素
s = Stack()
s.push(1)
s.push(2)
s.push(3)
s.push(4)
判断栈是否为空:
s.is_empty()
False
输出栈顶元素:
s.peek()
栈顶元素是:
4
输出栈的大小:
s.size()
栈的大小:
4
pop弹出栈元素:
# pop弹出元素
# 重复运行四次:依次输出4,3,2,1
s.pop()
1
再次判断栈是否为空:
s.is_empty()
True