逻辑结构分类
- 线性结构
对于数据结构而言,线性结构是n个数据元素的有序集合。
- 集合中必存在唯一的一个“第一个元素”;
- 集合中必存在唯一的一个“最后的元素”;
- 除最后元素之外,其他数据元素均有唯一的“后继”;
- 除第一个元素之外,其他数据元素均有唯一的“前驱”。
- 树形结构(层次结构)
树形结构指的是数据元素之间存在着“一对多”的树形关系的数据结构,是一类重要的非线性数据结构。在树形结构中,树根节点没有前驱节点,其余每个节点有且仅有一个前驱节点。叶子结点没有后续节点,其余每个节点的后续节点数可以是一个也可以是多个。 - 图状结构(网状结构)
图是一种比较复杂的数据结构。在图结构中任意两个元素之间都有可能有关系,也就是说这是一种多对多的关系。 - 其他结构
除了以上几种常见的逻辑结构之外,数据结构还包含其他的结构,比如集合等。有时根据实际情况抽象的模型不止是简单的某一种,也可能拥有更多的特征。
存储结构分类
- 顺序存储:将数据结构中各元素按照其逻辑顺序存放于存储器一片连续的存储空间中。
- 链式存储:将数据结构中各元素分布到存储器的不同点,用记录下一个节点位置的方式建立他们之间的联系。
- 索引存储:在存储数据的同时,建立一个附加的索引表,即索引存储结构=数据文件+索引表。
- 散列结构:根据数据元素的特殊字段(称为关键字key),计算数据元素的存放地址,然后数据元素按地址存放。
顺序结构和链式结构对比:
- 顺序结构适合遍历,但是不适合插入和修改;
- 链式结构适用插入操作或者大量数据的分散存储,但是遍历操作稍微复杂。
线性表
线性表的顺序存储
- 特点:
- 逻辑上相邻的元素ai,ai+1,其存储位置也是相邻的
- 存储密度高,方便对数据遍历查找
- 对表的插入和删除等云端效率较差
- 程序实现(list)
L=[1,2,3,4]
L.append(10)#尾部添加
L.insert(1,20)#插入元素
L.remove(3)#删除元素
L[4]=30#修改元素
L.index(2)#查找元素
线性表的链式存储
- 定义:将线性表中各元素分部在存储器的不同存储块,称为节点,每个节点(尾结点除外)中都持有一个指向下一个节点的引用。
- 特点
- 逻辑上相邻的元素ai,ai+1,其存储位置不一定相邻
- 存储稀疏,不必开辟整块存储空间
- 对表的插入和删除等运算的效率较高
- 逻辑结构复杂,不利于遍历
- 程序实现
- 节点如何表示?
自定义对象:对象即数据,对象属性即数据元素
数据元素:包含有用数据和记录笑一个对象地址的数据 - 如何建立关联
- 实现什么样的链表操作
单链表:
# 创建节点类
class Node:
def __init__(self, value):
self.value = value
self.next = None
# 创建单链表类
class SingleList:
def __init__(self):
self.head = Node(None) # 初始化头结点为空节点
self.length = 0 # 链表长度
# 给一个列表,初始化链表
def init_slist(self, l):
p = self.head
if not l:
return
for i in l:
p.next = Node(i)
self.length += 1
p = p.next
# 往链表里添加元素
# 在pre_item(节点的值)后面添加元素,元素节点的值为value
def append_slist(self, pre_item, value):
p = self.head.next
while p:
if p.value == pre_item:
n = Node(value)
n.next = p.next
p.next = n
self.length += 1
return
else:
p = p.next
return
# 头部插元素
def head_append(self, item):
p = self.head
n = Node(item)
n.next = p.next
p.next = n
self.length += 1
# 尾部插元素
def tail_append(self, item):
p = self.head
while p.next:
p = p.next
p.next = Node(item)
self.length += 1
# 从链表里删除元素
def delete_slist(self, value):
pre = self.head # 前一个节点
p = self.head.next # 从第一个节点开始
flag = False # 未找到标志位
while p:
if p.value == value:
pre.next = p.next
p = p.next
self.length -= 1
flag = True
break
else:
pre = p
p = p.next
if not flag:
print('value not in list')
# 链表判空
def is_empty(self):
if self.head.next is None:
return True
else:
return False
# 清空链表
def clear_slist(self):
self.head.next = None
self.length = 0
# 查询链表第几个节点的值(从1开始)
def search_slist(self, index):
if index > self.length:
error = "Index out of range"
return error
else:
p = self.head
count = 0
while count < index:
p = p.next
count += 1
return p.value
# 遍历链表
def travel_slist(self):
l = [] # 按链表顺序存放链表的值
p = self.head.next
while p != None:
l.append(p.value)
p = p.next
return l
栈和队列
栈
- 定义
栈是限制在一端进行插入操作和删除操作的线性表(俗称堆栈),允许进行操作的一端称为“栈顶”,另一固定端称为“栈底”,当栈中没有元素时称为“空栈”。 - 特点
- 栈只能在一端进行数据操作
- 栈模型具有先进后出的规律
- 栈的代码实现
栈的操作有入栈(压栈),出栈(弹栈),判断栈的空满等操作。
栈的顺序存储结构:
# 自定义栈异常
class StackError(Exception):
pass
# 基于列表实现顺序栈
class SStack:
def __init__(self):
# 约定列表最后一个元素为栈顶
self._elems = []
# 弹出栈顶元素
def top(self):
if not self._elems:
raise StackError('stack is empty')
return self._elems[-1]
# 判断栈是否为空
def is_empty(self):
return self._elems == []
# 入栈
def push(self, elem):
self._elems.append(elem)
# 出栈
def pop(self):
if not self._elems:
raise StackError('stack is empty')
return self._elems.pop()
栈的链式存储结构(头为栈顶):
# 自定义栈异常
class StackError(Exception):
pass
# 创建结点类
class Node:
def __init__(self, value):
self.value = value
self.next = None
class LStack:
def __init__(self):
# 标记栈顶位置
self._top = None
# 判空
def is_empty(self):
return self._top is None
# 入栈
def push(self, elem):
node = Node(elem)
node.next = self._top
self._top = node
# 出栈
def pop(self):
if self._top is None:
raise StackError('stack is empty')
p = self._top
self._top = p.next
return p.value
# 查看栈顶元素值
def top(self):
if self._top is None:
raise StackError('stack is empty')
return self._top .value
队列
- 定义:队列是限制在两端进行插入操作和删除操作的线性表,允许进行存入操作的一端称为“队尾”,允许进行删除操作的一端称为“队头”。
- 特点:
- 队列只能在队头和队尾进行数据操作
- 队列模型具有先进先出或后进后出的规律
- 队列的代码实现
队列的操作有入队,出队,判断队列的空满等操作。
队列的顺序存储结构:
# 队列异常
class QueueError(Exception):
pass
# 完成队列操作
class SQueue:
def __init__(self):
self._elems = [] # 使用列表存储队列数据
# 判空
def is_empty(self):
return self._elems == []
# 入队
def enqueue(self, elem):
self._elems.append(elem)
# 出队
def dequeue(self):
if not self._elems:
raise QueueError("Queue is empty")
return self._elems.pop(0)
队列的链式存储结构(链表尾添加元素为入队,头删除为出队):
# 队列异常
class QueueError(Exception):
pass
# 创建结点类
class Node:
def __init__(self, value):
self.value = value
self.next = None
# 头(front)指向的是队头的前一个,尾(rear)指向的是最后一个
# 头 = 尾 时为空
class LQueue:
def __init__(self):
self.front = self.rear = Node(None)
# 判空
def is_empty(self):
return self.front is self.rear
# 入队
def enqueue(self, elem):
self.rear.next = Node(elem)
self.rear = self.rear.next
# 出队
def dequeue(self):
if self.front == self.rear:
raise QueueError("Queue is empty")
self.front = self.front.next
return self.front.value
# 清空
def clear(self):
self.front = self.rear
补充:用两个栈实现队列
stack1=[]
stack2=[]
# 入栈
def push(val):
stack1.append(val)
# 出栈
def pop():
if stack2 == []:
while stack1 != []:
stack2.append(stack1.pop())
return stack2.pop()
树形结构
- 一个节点的子树的个数称为该节点的度,一棵树的度是指该树中节点的最大度数。
- 度数为零的节点称为树叶或终端节点,度数不为零的节点称为分支节点,出根节点之外的分支节点称为内部节点。
- 一个节点的子树之根节点称为该节点的子节点,该节点称为他们的父节点,同一节点的各个子节点之间称为兄弟节点。一棵树的根节点没有父节点,叶节点没有子节点。
- 一个节点系列k1,k2…ki,ki+1…kj,并满足ki是ki+1的父节点,就称为一条从k1到kj的路径,路径的长度为j-1,即路径中的边数。路径中前面的节点是后面节点的祖先,后面节点是前面节点的子孙。
- 节点的层数等于父节点的层数加一,根节点的层数定义为一。树中节点层数的最大值称为该树的高度或深度。
- m(m≥0)棵互不相交的树的集合称为森林。树去掉根节点就成为森林,森林加上一个新的节点就成为树。
二叉树
定义与特征
- 定义
二叉树(Binary Tree)是n(n≥0)个节点的有限集合,或者是空集(n=0),或者是由一个根节点以及两棵互不相交、分别称为左子树和右子树的二叉树组成。二叉树与普通有序树不同,二叉树严格区分左孩子和右孩子,机试只有一个子节点也要区分左右。 - 二叉树的特征
- 二叉树第i(i≥1)层上的节点最多为2i-1个。
- 深度为k(k≥1)的二叉树最多有2k-1个节点。
- 在任意一棵二叉树中,树叶的数目比度数为2的节点的数目多1。
- 满二叉树:深度为k(k≥1)时有2k-1个节点的二叉树。
- 完全二叉树:只有最下面两层有度数小于2的节点,且最下面一层的叶节点集中在最左边的若干位置上。
递归思想和实践
- 什么是递归?
所谓递归函数是指一个函数的函数体中直接调用或者间接调用了该函数自身的函数。这里的直接调用是指一个函数的函数体中含有调用自身的语句,间接调用是指一个函数在函数体里有调用了其它函数,而其它函数有反过来调用了该函数的情况。 - 递归函数调用的执行过程分为两个阶段
递推阶段:从原问题出发,按递归公式递推从未知到已知,最终到达到递归终止条件
回归阶段:按递归终止条件求出结果,逆向逐步代入到递归公式中,回归到原问题求解。 - 优点和缺点
优点:递归可以把问题简单化,让思路更清晰,代码更简洁
缺点:递归因系统环境影响大,当递归深度太大时,可能会得到不可预知的结果
二叉树的遍历
遍历:沿某条搜索路径周游二叉树,对树中的每一个节点访问一次且仅访问一次。
先序遍历:先访问树根,再访问左子树,最后访问右子树
中序遍历:先访问左子树,再访问树根,最后访问右子树
后序遍历:先访问左子树,再访问右子树,最后访问树根
层次遍历:从根节点开始,逐层从左向右进行遍历
重要代码:
from collections import deque
# 根节点
class TreeNode:
def __init__(self, data):
self.data = data
self.lchild = None
self.rchild = None
# 二叉树类
class BiTree:
def __init__(self, root):
self.root = root
# 判空
def is_empty(self):
if self.root is None:
return True
else:
return False
# 前序遍历
def pre_order(self, root):
if root is None:
return
print(root.data, end="")
self.pre_order(root.lchild)
self.pre_order(root.rchild)
# 中序遍历
def in_order(self, root):
if root is None:
return
self.in_order(root.lchild)
print(root.data, end="")
self.in_order(root.rchild)
# 后序遍历
def post_order(self, root):
if root is None:
return
self.post_order(root.lchild)
self.post_order(root.rchild)
print(root.data, end=" ")
# 层次遍历(队列思想)
def level_order(self, root):
q = deque()
q.append(root)
while len(q) > 0:
x = q.popleft()
print(x.data, end="")
if x.lchild:
q.append(x.lchild)
if x.rchild:
q.append(x.rchild)
已知前序遍历和中序遍历,重建二叉树:
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
# 返回构造的TreeNode根节点
def reConstructBinaryTree(self, pre, tin):
# write code here
if not pre or not tin:
return None
root=TreeNode(pre.pop(0))
index=tin.index(root.val)
root.left=self.reConstructBinaryTree(pre,tin[:index])
root.right=self.reConstructBinaryTree(pre,tin[index+1:])
return root
补充面试题:
- 给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
class TreeLinkNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
self.next = None
class Solution:
def GetNext(self, pNode):
# write code here
if pNode.right:# 如果有右子树,找最左边的叶子结点
p=pNode.right
while p.left:
p=p.left
return p
else:# 如果没有右子树,如果节点是父节点的左子节点,那么它的下一个节点是父节点
while pNode.next:
if pNode.next.left==pNode:
return pNode.next
pNode=pNode.next
return