【408 go through】基础数据结构
2018-03-26 15:39
344 查看
0x00 基本内容
数据结构包含 逻辑结构,存储结构,和数据运算。逻辑结构独立于储存结构,存储结构依赖与逻辑结构来实现。
1.逻辑结构
细分为:- 线性结构
- 树形结构
- 图结构
- 集合结构
2. 储存结构
又叫物理结构:- 顺序储存:空间连续。可随机存取。容易产生内存碎片。
- 链式储存:空间不连续,借助地址前后连接。空间利用比较灵活。
- 索引储存:还附加索引项,结构为(key,value/地址)
- 散列储存:利用key直接计算地址(hash)
3.算法
求解问题的步骤,是有限的指令序列。T(n)描述基本运算的频度,n为问题的规模。
复杂度分析问题消耗资源的数量级。
时间复杂度分析T(n)的数量级。
- 最坏时间复杂度。
- 最好时间复杂度。
- 平均时间复杂度:假设所有输入等概率出现的期望运行时间。
空间复杂度分析存储空间的消耗。原地工作指消耗常数空间。
0x01 一般线性表
具有相同数据类型的n个数据元素的有限序列。其中n为表长度。n=0 时表示一个空表。L=(a1,a2,…ai,…,an) ,1~n .
除了第一个元素,每个元素都有一个直接前驱。同理除了最后一个元素其他都有一个直接后继。
线性逻辑结构,根据储存结构的不同分为顺序存储和链式存储。
1.顺序表示——顺序表
线性表的顺序存储。使用地址连续的储存单元(数组),依次储存线性表中的数据元素。逻辑相邻的两个元素物理上也相邻。数组下标从0开始,线性表逻辑上从1开始。
数组空间可以静态分配也可以动态分配。(malloc || new )
顺序表最大的特点是支持随机访问,但是因为存储密度大,每次插入和删除都要移动大量元素。
2.链式表示
链式存储的数据存放地址不连续,随机分配,但每个元素(节点)附加了一项地址(指针),将各项数据连接成链。对插入、删除操作比较方便,不需要大量移动数据元素。但是只能支持顺序访问 ,每次都要从头开始遍历到需要找的那个元素。
a. 单链表
连接方向为单向。
typedef struct LNode{ ElemType data ; struct LNode *next ; }LNode, *LinkList ;
通常直接使用一个头指针 * LinkList 来表示一个链表,指向链表的第一个结点。如果指针=NULL ,则表示一个空表。
另外,为了统一所有结点以及空表与非空表在代码上的操作,可以在第一个元素之前给单链表附加一个头结点 , 头节点的next指针指向链表第一个元素。
这样,头结点就成为了单链表上的第一个结点(data 不包含任数据)。头指针始终指向头结点,头结点的next 指向第一个元素 , 此时如果为空表 ,则头指针的next==NULL。
单链表建立方法分为头插法和尾插法 :
头插法: 从空表开始,每次插入的数据都放在头结点之后 。
尾插法: 每次插入位置都在表尾,需要在插入时维持一个尾指针指向链表尾结点。
通常使用尾插法,另外也可以通过进行尾插法然后交换数据变为逻辑上的头插法
b. 双链表
单链表,方向单一,只能后向遍历进行访问,当需要访问前驱节点时就需要重新遍历。为了克服该缺点引入双向链表。结点中有两个指针prior 和 next 。
typedef struct DNode{ ElemType data; struct DNode *prior ,* next; }DNode , *DLinkList;
c.循环链表
循环单链表最后一个结点的指针不是NULL,而是改为指向头结点,形成环。表中没有值为NULL的next指针。
循环双链表中类似,末尾结点next指向头结点,头结点的prior 指向末尾结点。
d. 静态链表
借助数组来描述链式结构 。因此需要预先分配较大一片连续空间。
typedef struct { ElemType data; int next; } SLinkList[MaxSize] ;
指针使用数组的下表来代替,SLinkList[0]为链表的“头结点”,其next指向第一个元素。末尾节点的next使用 -1 表示结束。 显然 SLinkList[0]->next=-1 表示空表。
在不支持指针的语言中这是比较巧妙的设计。
0x02 栈Stack(特殊的、受限的线性表)
首先栈属于线性表的一种,但是受到一些限制——只能在顶部进行插入和删除(FILO先进后出)。逻辑上不允许遍历,只能对栈顶进行访问 。
1. 顺序栈
事先在创建时分配一组连续的空间(数组)存放数据元素,指针指向栈顶。typedef struct{ Elemetype data[MaxSize]; int top; //非链式储存当然是用数组下表表示“指针” }SqStack;
栈空时 top = -1 ,栈满 top=MaxSize-1 。
2. 共享栈
两个栈共用一组连续空间,其中一个栈从下向上生长,另一个从上向下生长,节约空余空间。当两个栈top1-top0=1时,则表明栈的空间已经满了。top0的初始值为-1, top1为* MaxSize* 。
3. 链式栈
链式存储的栈,通常使用单链表实现。由于是链式存储,空间使用不受限制,因而不存在上溢。
typedef struct Linknode{ Elemtype data; struct Linknode * next; } *LinkStack;
因为不需要遍历操作,所以所有操作都在表头实现,只需要将top指针指向表头,插入时为前插法即可。
4. extra. 出栈序列问题
入栈序列为 (1,2,3,… ,n),共可以有多少种出栈序列 ?这是一个关于卡特兰(Catalan)数的问题。共有C(n,2n)/n+1个可能序列。
第i个出栈元素为Xi,则序列后边所有小于Xi的元素必然递减排列。
实际上Xi出栈时,则出栈序列的后续所有比Xi的小元素必然还在栈中,并自栈底向栈顶递增,所以出栈必定递减排列。
0x03 队列Queue
同样也是首先的线性表,只能先进先出(FIFO)——在队首进队尾出。不能队中间的元素进行操作,只能访问队头,队头出队,队尾入队。
1.顺序存储的队列
数组中,使用两个下标front 和 rear 分别作为队头位置和队尾的下一个位置 。这样当队列为空时 rear=front ,队长为 rear-front。但是当rear=Maxsize 之后,虽然数组空间仍可能有余但是整个队列已经不能再进行入队操作了。这就产生了假上溢。
引入循环队列。
为了解决上述的假上溢问题,对rear 和 front 的加减操作引入了mod 运算,令其得以循环。
初始时:rear=front =0
出队 : front = (front+1)%MaxSize
入队 : rear = (rear+1)%MaxSize
求队长:rear+MaxSize-front%MaxSize
不过这样又产生了一个问题:队满队空时都是rear=front。
但是仔细观察我们会发现一个事实,进行mod运算之后队长的表示范围只支持 0~MaxSize-1 。那么我们牺牲一个元素的空间,约定 队长=MaxSize-1 表示队伍已满即可。
2. 链式队列
实际上,这是一个带有头指针和尾指针的单链表,利用头指针进行出队删除操作,利用尾指针进行入队插入操作。typedef struct { Elemtype data; struct LinkNode *next; }LinkNode; typedef struct { LinkNode * front,*rear; }LinkQueue;
当front=NULL ,rear=NULL 时,队列为空。
使用头结点时,front始终指向头结点,当front=rear时即表示为空表。
LinkQueue 可以增加一个整型表示队列元素数量,值为0时为空表。
3.双端队列
两端都可以进行入队和出队操作。输出受限的双端队列:只有一端能进行出队操作。
输入受限的双端队列:只有一端能进行入队操作。
受限的双端队列出队问题:
入队序列为1,2,3,4。
能由输人受限的双端队列得到,但不能由输出受限的双端队列得到的序列为:4132
能有输出受限的双端队列得到,但不能由输入受限得到的序列为:4213
既不能由输出受限,也不能由输入受限的双端队列得到的出队序列为:4231 .
0x04. 栈和队列,数组的一些应用
1. 栈,递归,括号匹配和表达式
递归是一种重复自身调用自身的方法,通常用于将问题变为更小规模的相同问题进行求解,再组合结果以得到当前规模下的解。递归的代码虽然简洁易懂,但是效率并不高(出入栈状态保存消耗,子状态可能会重复求解,层数过多也容易产生栈溢出)。
通常我们可以将递归问题转为相同的非递归问题,由于递归本身是用栈实现的,我们可以通过栈来模拟递归过程,通常对于单向递归和尾递归也可以直接自下往上用迭代求解,以得到当前参数下的解。
1)括号匹配问题(求解括号嵌套序列的正确性):
主要思想是,顺序访问括号序列,找到一个左括号时,:
指针向右进1,寻找对应的右括号。
如果又出现一个左括号,从该指针位置开始递归调用自身,如果该调用返回flase,则自己也返回false。否则从1开始,指针继续向右。
如果此时出现右括号不匹配,返回false。
如果此时括号匹配,返回true。
如果指针到达末尾仍未匹配到,返回false。
将该方法定义为match( i ),则一开始调用match(0)即可,返回false即序列错误。
2)运算表达式问题
中缀表达式转换为后缀表达式问题
中缀表达式即是我们常见的表达式形式 ( e.g. A+B*C
将其转换为后缀表达式: ABC*+
转化过程需要利用一个栈保存操作符保证运算优先级,一般过程见王道数据结构。
后缀表达式求值
同样利用一个栈,从左往右,如果遇到操作数则入栈,如果遇到操作符,则将栈弹出两次,得到两个操作数根据操作符进行运算,把结果压回栈中,直到最后到达末尾。
2. 队列,层次遍历(广度优先),缓冲队列
层次遍历二叉树步骤:根节点入队。
队列出队一个节点,访问其中数据,若左节点存在,讲左节点入队,右节点同样。
重复步骤2,直到队列为空。
3.矩阵的压缩存储
矩阵一般用一个二维数组来存储。元素具有某些特定分布规律的矩阵,可以通过一些手段进行压缩以节省空间。
对称矩阵
由于其对称性,可以只保存其中的上(下)三角元素,到一个一维数组B[n(n+1)/2]中。
上(下)三角矩阵
除了三角区其余全部为同一常量。所以相比对称矩阵,需要多一个单位空间来保存该常量。所以耗费B[n(n+1)/2+1]。
三对角矩阵
带状矩阵。三条对角线。
稀疏矩阵
非零元素只占很小的一部分,直接使用二维数据会浪费大量空间。所以可以使用三元组(i,j,value)的列表来储存非零元素。但是这样会失去随机访问的特性,增加访问、查找的复杂度。
相关文章推荐
- 数据结构 基础题 查找
- 数据结构基础PTA 6-1 单链表逆转
- 基础算法(二)---数据结构之散列
- 【数据结构基础】二叉排序(搜索)树
- 中国大学MOOC-数据结构基础习题集、04-2、File Transfer
- 基础数据结构
- 数据结构基础4_双链表的实现
- 【程序员面试宝典】数据结构基础1:链表
- Uva 算法入门经典(数据结构基础)线性表题目
- java基础巩固训练营【第一轮】(七) 数据结构中的java
- 数据结构基础2:栈
- 数据结构基础温故-1.线性表(上)
- 数据结构基础知识
- Python基础数据结构(list, str, tuple, dict)
- 数据结构基础知识(1)
- 算法竞赛入门经典:第六章 数据结构基础 6.4测试
- 第六章:数据结构基础。第二部分
- 数据结构之二叉树基础二
- 中国大学MOOC-数据结构基础习题集、09-2、QQ帐户的申请与登陆
- 【z转载】学习STL map, STL set之数据结构基础