大话数据结构阅读笔记

205 阅读14分钟

单链表

单链表第i个数据插入节点的算法思路

1 声明一节点P指向链表第一个结点,初始化j从1开始
2 当j<1时,就遍历链表,让p的指针向后移动,不断指向下一节点,j累加1
3 若到链表末尾p为空,则说明第i个元素不存在
4 否则查找成功,在系统中生成一个空节点s
5 将数据元素e赋值给s->data
6 单链表的插入标准语句 s->next = p->next p->next = s
7 返回成功

单链表第i个数据删除节点的算法思路

1 声明一结点p指向链表第一个结点,初始化j从1开始
2 当j<1时,就遍历链表,让p的指针向后移动,不断指向下一个结点,j累加1
3 弱到链表末尾p为空,则说明第i个元素不存在
4 否则查找成功,将欲删除的节点p->next赋值给q
5 单链表的删除标准语句 p->next = q->next(q->next == p->next->next)
6 将q节点中的数据赋值给e,作为返回
7 释放q节点
8 返回成功

说明:对于插入或删除数据越频繁的操作,单链表的效率优势就越是明显。

单链表整表创建的算法思路

1 声明一节点p和计数器变量i
2 初始化一空链表L
3 让L的头节点的指针指向NULL,即建立一个带头节点的单链表
4 循环:
	生成一新节点赋值给P
    随机生成一数字赋值给p的数据域P->data
	将p插入到头结点与前一新结点之间

单链表的整表删除的算法思路

1 声明一结点p和q
2 将第一个节点赋值给p
3 循环
	将下一节点赋值给q
    释放p
    将q赋值给p

单链表结构与顺序存储结构优缺点

存储分配方式:
	顺序存储结构:用一段连续的存储单元依次存储线性表的数据元素
    单链表:采用链式存储结构,用一组任意的存储单元存放线性表的元素
时间性能:
	查找:
    	顺序存储结构O(1)
		单链表O(n)
	插入和删除:
    	顺序存储结构需要平均移动表长一半的元素,时间为O(n)
		单链表在献出某位置的指针后,插入和删除时间仅为O(1)
	空间性能:
    	顺序存储结构需要预分配存储空间,分大了,浪费,分效率易发生溢出
        单链表不需要分配存储空间,只要有就可以分配,元素个数也不受限制
        
结论:
	若线性表需要频繁查找,很少进行插入和删除操作时,适宜采用顺序存储结构,
    若线需要频繁插入和删除时,适宜食用单链表结构
    
    当线性表中的元素个数变化较大或者根本不知道有多大时,最好用单链表结构。相反事先知道线性表的大致长度,用顺序存储结构效率会高很多。

静态链表

用数组描述的链表叫做静态链表

example:

静态链表的插入操作

静态链表的删除操作

静态链表的优缺点

优点: 在插入和删除操作时,只需要修改游标,不需要移动元素,从而改进了在顺序存储结构中的插入和删除操作需要移动大量元素的缺点
缺点:没有解决连续存储分配带来的表长难以确定的问题
	失去了顺序存储结构随机存取的特性

循环链表

将单链表中终端节点的指针端由空指针改为指向头节点,就使整个单链表形成一个环,这种头尾相接的单链表称为单循环链表,简称循环链表。

双向链表

双向链表是在单链表的每个结点中,在设置一个指向其前驱节点的指针域

双向链表的插入操作

顺序
1 先搞定s的前驱和后继
2 搞定后结点的前驱
3 搞定前结点的后继

双向链表的删除操作

总结

栈:栈是限定仅在表尾(栈顶)进行插入和删除操作的线性表(先进后出,后进先出)

队列:队列是只允许在一端进行插入操作、而在另一端进行删除操作的线性表

栈的顺序存储结构

两栈共享空间

如果我们有两个相同类型的栈,我们为它们各自开辟了数组空间,极有可能是第一个栈已经满了,在进栈就溢出了,而另一个栈还有很多存储空间空闲。

top2从左到右 0-n top2为n时top2为空栈,top2为0时top2栈满

空间满的条件为 top1+1==top2

栈的链式存储结构

栈的链式存储结构----进栈操作

1 把当前的栈顶元素赋值给新结点的直接后继,如图1
2 将新的节点s赋值给栈顶指针 如图2

栈的链式存储结构----出栈操作

1 将栈顶结点赋值给p,如图3
2 使得栈顶指针下移一位,指向后一结点,如图4
3 释放结点p

说明:如果栈的使用过程中元素变化不可预料,有时很小,有时非常大那么最好是用链栈,反之,如果它的变化在可控范围内,建议使用顺序栈会更好一些

栈的作用----后缀表达式

ex:  9 3 1-3 * + 10 2 / +
规则:从左到右遍历表达式的每个数字和符号,遇到是数字就进栈,遇到是符号,就将处于栈顶两个数字出栈进行运算,运算结果进栈,一直到最终获得结果。
1 初始化一个空栈。此栈用来对要运算的数字进出使用
2 后缀表达式中前三个都是数字,所以 9 3 1 进栈
3 接下来是“——”,所以将站中的1出栈作为减数,3出栈作为被减数,并运算3-1得到2,在将2进栈
4 接着是数字3进栈
5 后面是*,也就意味着栈中32出栈,23相乘,得到6,并将6进栈
6 下面是"+",所以栈中69出栈,96相加,得到15,将15进栈
7 接着是102两数字进栈
8 接下来是"/",因此,栈顶的210出栈,102相除,得到5,将5进栈
9 最后一个符号是"+",所以155出栈并相加,得到20,将20进栈
10 结果是20出栈,栈变为空

图示:

中缀表达式转后缀表达式

9+(3-1)*3+10/2 中缀表达式 ---> 9 3 1-3 * + 10 2 / +
规则:从左到右遍历中缀表达式的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分;若是符号,则判断其与栈顶符号的优先级,是右括号或优先级低于栈顶符号(乘除优先加减)则栈顶元素一次出栈并输出,并将当前符号进栈,一直到最终输出后缀表达式为止

步骤:
1 初始化一空栈,用来对符号进出栈使用
2 第一个字符是数字9,输出9,后面是符号"+",进栈
3 第三个字符是"(",依然是符号,因其只是左括号,还未配对,故进栈
4 第四个字符是数字3,输出,总表达式为 9 3 接着是 "——",进栈
5 接下来是数字1,输出,总表达式为 9 3 1,后面是符号")",此时,我们需要去匹配此前的"(",所以栈顶依次出栈,并输出,直到"("出栈为止。此时左括号上方只有"——",因此输出"——".总的输出表达式为 9 3 1 -
6 接着是数字3,输出,总的表达式为 9 3 1 - 3 紧接着是符号"×",因为此时的栈顶符号为"+"号,优先级低于"+",因此不输出,"×"进栈
7 之后是符号"+",此时当前栈顶元素"*"比这个"+"的优先级高,因此栈中元素出栈并输出

图示:

队列

队列:队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表(先进先出,FIFO)

循环队列

解决假溢出:我们把队列的头尾相接的顺序存储结构称为循环队列

判断队列满的条件是(rear+1)%QueueSize == front
计算队列长度公式为:(rear-front+QueueSize) % QueueSize

链队列

队列的链式存储结构,其实就是线性表的单链表,只不过它只能是尾进头出而已,我们把它简称为链队列。

队列的链式存储结构----入队操作

1 把拥有元素e新结点s赋值给原队尾结点的后继 见上图1
2 把当前的s设置为队尾结点,rear指向s 见上图2

队列的链式存储结构----出队操作

1 将欲删除的队头结点暂存给p
2 将原队头结点后继p->next 赋值给头结点后继
3 若队头是队尾,则删除后将rear指向头结点

栈和队列的区别

栈是限定仅在表尾进行插入和删除操作的线性表
	对于栈来说,如果是两个相同数据类型的栈,则可以用数组的两端作栈底的方法来让了两个栈共享数据,这就可以最大化的利用数组的空间
    
队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表:
对于队列来说,为了避免数组插入和删除需要移动数据,于是就引入了循环队列,使得头和队尾可以在数组中循环变化,解决了移动数据的时间损耗。

栈:两栈共享空间和链栈
队列:顺序队列 循环队列 链队列

串:串是由零个或多个字符组成的有限序列,又名叫字符串

朴素模式匹配算法

复杂度为O((n-m+1)*m)

KMP模式匹配算法

复杂度为O(n+m)

next数组值推导

KMP匹配算法优化

nextval数组值推导

总结改进过的KMP算法,它是计算出next值的同时,如果a位字符与它next值指向的b位字符相等,则该a位的nextval就指向b位的nextval值。如果不等,则该a位的nextval值就是它自己a位的next的值

树是n(n>=0)个结点的有限集。n=0时称为空树。在任意一棵非空树中:
(1) 有且仅有一个特定的称为根的结点
(2) 当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1,T2,Tn,其中每一个集合本身又是一棵树,并且称为根的子树。

结点分类

线性结构和树结构的差别

线性结构:
第一个数据元素:无前驱
最后一个数据元素:无后继
中间元素:一个前驱一个后继

树结构:
根结点:无双亲,唯一
叶结点:无孩子,可以多个
中间结点:一个双亲多个孩子

树的存储结构

双亲表示法

孩子表示法

每个结点指针域的个数等于该结点的度,我们专门取一个位置来存储结点指针域的个数,格式为data->degree->child1...  其中data为数据域,degree为度域,也就是存储该结点的孩子结点的个数,child1到childd为指针域,指向该结点的各个孩子的结点。

把每个结点的孩子结点排列起来,以单链表作存储结构,则n个结点有n个孩子链表,如果是叶子结点则此单链表为空。然后n个头指针又组成一个线性表,采用顺序存储结构,存放进一个一维数组中

如上图
孩子链表的孩子结点 child->next
其中child是数据域,用来存储某个结点在表头数组中的下标。next是指针域,用来存储指向某结点的下一个孩子结点的指针
表头数组的表头结点 data->firstchild
其中data是数据域,存储某结点的数据信息。firstchild是头指针域,存储该孩子链表的头指针

双亲孩子表示法

孩子兄弟表示法

data->firstchild->rightsib
其中data是数据域,firstchild为指针域,存储该结点的第一个孩子结点的存储地址,rightsib是指针域,存储该节点的右兄弟结点的存储地址

二叉树

二叉树是n(n>=0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树的二叉树组成

满二叉树和完全二叉树

二叉树的特点:
	1.叶子结点只能出现在最下两层
    2.最下层的叶子一定集中在左部连续位置
    3.倒数二层,若有叶子结点,一定都在右部连续位置
    4.如果结点度为1,则该结点只有左孩子,即不存在只有右子树的情况
    5.同样结点树的二叉树,完全二叉树的深度最小

二叉树的性质

1.在二叉树的第i层上至多有2^(i-1)个结点(i>=1)。
2.深度为k的二叉树至多有2^k-1个结点(k>=1).
3.对任何一棵二叉树T,如果其终端结点数为n0,度为2的结点树为n2,则n0=n2+1。
	分支线总数:n-1=n1+2n2 n=n0+n1+n2 所以 n0=n2+1
4.具有n个结点的完全二叉树的深度为[log2^n]+1(|x|表示不大于x的最大整数).
5.如果对一棵有n个结点的完全二叉树(其深度为|log2^n|+1)的结点按层序编号(从第1层到第|log2^n|+1层,每层从左到右),对任一节点i(1<=i<=n)有:
	1.如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲是结点[i/2]。
    2.如果2i>n,则结点i无左孩子(结点i为叶子结点);否则其左孩子是结点2i。
    3.如果2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1。如下图所示

二叉树的存储结构

顺序存储结构一般只用于完全二叉树

二叉链表

二叉树每个结点最多有两个孩子,所以为它设计一个数据域和两个指针域是比较自然的想法,我们称这样链表叫做二叉链表。
其中data是数据域,lchild和rchild都是指针域,分别存放指向左孩子和右孩子的指针。

遍历二叉树

二叉树的遍历是指从根节点触发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次=

前序遍历

规则是若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,在前序遍历右子树,如下图所示,遍历顺序为:ABDGHCEIF.

中序遍历

规则是若树为空,则空操作返回,否则从根结点开始(注意并不是先访问根结点),中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树,如下图所示,遍历的顺序为:GDHBAEICF

后序遍历

规则是若树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根结点。如下图所示,遍历的顺序为GHDBIEFCA.