栈(stack)是限定仅在表尾进行插入和删除操作的线性表
栈是一种后进先出(Last In First Out)的线性表,简称LIFO结构
栈的顺序存储结构如下图
栈的链式存储结构如下图
比较:
程序中解决四则运算是比较麻烦的,因为计算有优先级,波兰逻辑学家发明了一种不需要括号的后缀表达法,称为逆波兰表示
如
9 + (3 - 1) x 3 + 10 ÷ 2
转换成后缀表达式为
9 3 1 - 3 * + 10 2 / +
转换规则:
从左到右遍历表达式的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分,若是符号,则判断其与栈顶符号的优先级,是右括号或优先级低于栈顶符号(乘除优先加减)则栈顶元素依次出栈并输出,并将当前符号进栈,一直到最终输出后缀表达式为止
计算规则:
从左到右遍历表达式的每个数字和符号,遇到是数字就进栈,遇到是符号,就将处于栈顶两个数字出栈,进行运算,运算结果进栈,一直到最终获得结果
队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表
队列是一种先进先出(First In First Out)的线性表,简称FIFO
允许插入的一端称为队尾,允许删除的一端称为队头
队列的头尾相接的顺序存储结构称为循环队列
队列的链式存储结构,就是线性表的单链表,只不过它只能尾进头出
比较
串(string)是由零个或多个字符组成的有限序列,又名叫字符串
串的比较是通过组成串的字符之间的编码来进行的,而字符的编码指的是字符在对应字符集中的序号(如ASCII值)
串的存储结构与线性表相同,分为两种:串的顺序存储结构和串的链式存储结构
串的顺序存储结构是用一组地址连续的存储单元来存储串中的字符序列,按照预定义的大小,为每个定义的串变量分配一个固定长度的存储区,一般是用定长数组来定义
串结构中的每个元素数据是一个字符,如果一个结点对应一个字符,就会存在很大的空间浪费,因此可以考虑一个结点存放多个字符,最后一个结点若是未被占满时,可以用”#”或其他非串值字符补全,如下图所示
每个结点存多少个字符会直接影响串处理的效率,需要根据实际情况做出选择
串的链式存储结构除了在连接串与串操作时有一定方便之外,总的来说不如顺序存储灵活,性能也不如顺序存储结构好
子串的定位操作通常称做串的模式匹配,如从主串S=”goodgoogle”中,找到子串T=”google”这个子串的位置,通常需要下面的步骤
时间复杂度为O(n+m),其中n为主串长度,m为要匹配的子串长度
极端情况下,主串为S=”00000000000000000000000001”,子串为T=”0001”,在匹配时,每次都得将T中字符循环到最后一位才发现不匹配,此时的时间复杂度为O((n-m+1)*m)
树(Tree)是n(n≥0)个结点的有限集合。n=0时称为空树,在任意一棵非空树中:
树的结点包含一个数据元素及若干指向其子树的分支,结点拥有的子树数称为结点的度(Degree),度为0的结点称为叶结点(Leaf)或终端结点;度不为0的结点称为非终端结点或分支结点
树的度是树内各结点的度的最大值
结点的子树的根称为该结点的孩子(Child),相应地,该结点称为孩子的双亲(Parent)
同一个双亲的孩子之间互称兄弟(Sibling)
结点层次(Level)从根开始定义起,根为第一层,根的孩子为第二层。若某结点在第i层,则其子树的根就在第i+1层
在同一层的结点互为兄弟
如果将树中结点的各子树看成从左至右是有次序的,不能互换的,则称该树为有序树,否则称为无序树
森林(Forest)是m(m≥0)棵互不相交的树的集合
线性结构
树结构
在每个结点中,附设一个指示器指示其双亲结点到链表中的位置
该存储方式根据结点的parent指针很容易找到它的双亲结点,时间复杂度为O(1)
缺点: 如果需要知道某个结点的所有孩子,需要遍历整棵树
把每个结点的孩子结点排列起来,以单链表作存储结构,则n个结点有n个孩子链表,如果是叶子结点则此单链表为空,然后n个头指针又组成一个线性表,采用顺序存储结构,存放进一个一维数组中,如下图所示
缺点: 如果需要知道某个结点的双亲,需要遍历整棵树
改进: 双亲孩子表示法
任意一棵树,它的结点的第一个孩子如果存在就是唯一的,它的右兄弟如果存在也是唯一的,因此,可以设置两个指针,分别指向该结点的第一个孩子和此结点的右兄弟
这个表示法的最大好处是它把一棵复杂的树变成了一棵二叉树
二叉树(Binary Tree)是n(n≥0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树的二叉树组成
完全二叉树
满二叉树一定是一棵完全二叉树,但完全二叉树不一定是满二叉树
如果对一棵有n个结点的完全二叉树的结点按层序编号(每层从左到右),对任一结点i(1≤i≤n)有:
二叉树的顺序存储结构就是用一维数组存储二叉树中的结点,并且结点的存储位置,也就是数组的下标要能体现结点之间的逻辑关系,比如双亲与孩子的关系,左右兄弟的关系等
上图浅色代表不存在的结点,不存在的结点用^表示,会造成对存储空间的浪费,所以顺序存储结构一般只用于完全二叉树
二叉树每个结点最多有两个孩子,所以为它设计一个数据域和两个指针域
二叉树的遍历是指从根结点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次
先访问根结点,然后前序遍历左子树,再前序遍历右子树
从根结点开始(并不是先访问根结点),中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树
从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根结点
从树的第一层,也就是根结点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问
在二叉链表上,只能知道每个结点指向其左右孩子结点的地址,而不知道某个结点的前驱是谁,后继是谁,可以利用如下结构,存放指向结点在某种遍历次序下的前驱和后继结点的地址
这种指向前驱和后继的指针称为线索,加上线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树
如果所用的二叉树需经常遍历或查找结点时需要某种遍历序列中的前驱和后继,就比较适合用线索二叉链表的存储结构
将树转换为二叉树的步骤如下
森林是由若干棵树组成的,所以完全可以理解为,森林中的每一棵树都是兄弟,可以按照兄弟的处理办法来操作
步骤如下:
二叉树转换为树是树转换为二叉树的逆过程
步骤如下:
判断一棵二叉树能够转换成一棵树还是森林,标准很简单,只要看这棵二叉树的根结点有没有右孩子,有就是森林,没有就是一棵树,转换成森林的步骤如下:
树的遍历分为两种方式
森林的遍历也分为两种方式
森林的前序遍历和二叉树的前序遍历结果相同,森林的后序遍历和二叉树的中序遍历结果相同
从树中一个结点到另一个结点之间的分支构成两个结点之间的路径,路径上的分支数目称做路径长度
树的路径长度就是从树根到每一结点的路径长度之和
如果考虑到带权的结点,结点的带权的路径长度为从该结点到树根之间的路径长度与结点上权的乘积,树的带权路径长度为树中所有叶子结点的带权路径长度之和
带权路径长度WPL最小的二叉树称做赫夫曼树
二叉树a的 WPL = 5x1+15x2+40x3+30x4+10x4 = 315
二叉树b的 WPL = 5x3+15x3+40x2+30x2+10x2 = 220
构造赫夫曼树的步骤:
此时构造出来的赫夫曼树的 WPL = 40x1+30x2+15x3+10x4+5x4 = 205