数据结构
1.绪论
时间复杂度和空间复杂度
1.时间复杂度:算法中基本操作的执行次数作为算法时间复杂度的度量。(算法中的基本操作和问题规模都会影响时间复杂度)
O(1)<=O(log2(n))<=O(n)<=O(n log2(n))<=O(n2)<=……<=O(nk)<=O(2n)
2.空间复杂度:指算法在运行时所需储存空间的度量,主要考虑在算法运行过程中临时占用的存储空间的大小。
基本概念
1.数据:
数据是对客观事物的符号表示,在计算机中是指所有能输入到计算机中并且被计算机程序处理的符号的总和。
2.数据元素:
是数据的基本单位,是数据结构这门课中讨论的最小单位,在计算机程序中通常被当作一个整体进行考虑和处理。一个数据元素可以有若干个数据项组成。
3.数据对象:
相同性质的数据元素的集合,是数据的一个子集。
4.数据类型:
是数据的分类,对于基本的数据类型,如:整形int、long,字符型char,浮点型float、double等。其他还有结构型和指针型。
5.数据结构:
是指相互之间存在一种或多种数据关系数据元素的集合。数据结构包括逻辑机构、存储结构和对数据的运算三个方面。
6.数据的逻辑结构:
(1)线性结构:是一个数据元素有序集合,它具有四个特征:存在唯一的第一个元素、最后一个元素,唯一的前驱、后继。数据结构中线性结构是指数据元素之间存在着“一对一”的线性关系的数据结构。(2)非线性结构:非线性结构中的结点存在着一对多的关系,它又可以细分为树形结构和图形结构。
7.数据的物理结构:
又称为存储结构,是数据的逻辑结构在计算机中的表示(顺序映像——顺序存储;非顺序映像——链式存储、索引存储、散列存储)。
算法
1.算法:
有基本运算及规定的运算顺序所构成的完整的解题步骤,或看成按照要求计算好的有限的确切的计算序列。
2.算法特性:
有穷性、确定性、可执行、输入、输出。
3.算法的设计目标:
正确性、可读性、健壮性(很好的容错率,对不合理的数据进行检查)和算法效率(高效率和低存储量)。
2.线性表
基本概念与实现
1.线性表:
具有相同特性数据元素的一个有限序列。
2.线性表特性:
只有一个表头元素,只有一个表尾元素,表头元素没有前驱,表尾元素没有后继,其他元素只有一个前驱、一个后继。
3.线性表存储结构:
(1)顺序表:把线性表得所有元素按照其逻辑顺序,依次存储到从指定的存储位置开始的一块连续的存储空间中。(2)链表:每个结点不仅包含所存元素的信息还包含逻辑关系的信息,这样就可以通过他的前驱地址和后继地址找到他相邻的元素。(3)区别:顺序表可以随机访问,且占用连续的存储空间,存储分配只能预先进行。链表不支持随机访问,结点空间利用率比顺序表较低,支持存储空间动态分配。顺序表在插入删除时要移动多个元素,链表不需要。
4.链表有五种形式
(1)单链表:
带头结点的单链表:
head--->头结点--->开始结点--->…--->终端节点
空链:head->next==NULL
不带头结点的单链表:
head--->开始结点--->…--->终端节点
空链:head==NULL
(2)双链表:单链表只能由开始结点遍历到终端节点,而不能从终端节点遍历到开始结点,而双链表就是为了解决这类问题,在单链表的节点上添加一个前驱指针域,这样就方便结点通过前驱指针域找到前驱结点。
带头结点空链:head->next==NULL
不带头结点空链:head==NULL
(3)循环单链表:终端节点的next指向第一个结点。
空链:
带头结点headnext==head
不带头结点:head==NULL
(4)循环双链表:终端节点的next指向第一个结点,第一个节点的prior指向终端节点。
空链:①head->next==head;
②head->prior==head;
③head->next==head&&head->prior==head;
④head->next==head || head->prior==head。
(5)静态链表:借助数组实现。
地址 | data | 指针 |
---|---|---|
0 | A | 2 |
1 | F | 7 |
2 | B | 3 |
3 | C | 4 |
4 | D | 5 |
5 | E | 1 |
6 | H | -1 |
7 | G | 6 |
8 | ||
9 |
5.顺序表与链表的比较
(1)基于空间的比较:
存储分配方式:顺序表的存储空间是一次性分配,链表的存储空间是多次分配。
存储密度:结点值域所占的存储量/结点所占的存储量。顺序表=1;链表<1(因为结点中有指针域)。
(2)基于时间的比较:
存储方式:线性表可以随机存取,也可以顺序存取;链表只能顺序存取。
插入/删除:线性表插入删除时间复杂度O(n),期望E=(n-1)/2。
线性表结构体定义和基本操作
顺序表:
typedef struct Sqlist
{
Int data[maxsize];
Int lenght;
}
初始化:
Viod initList(Sqlist &L)
{
L.lenght = 0;
}
查找:
Int findElem(Sqlist L, int e)
{
Int i;
for(i = 0; i<L.lenght; i++)
{
If(e == L.data[i])
return i;
}
return -1;
}
插入:
Int insertElem(Sqlist &L, int e, int p)
{
If(p>L.lenght || p<0 || L.lenght == maxSize)
Return 0;
For(i = L.lenght – 1; i >= p; i--)
{
L.data[i+1] = L.data[i];
}
L.data[p] = e;
(L.lenght)++;
Return 1;
}
删除:
Int deleteElem (Sqlist &L, int p, int &e)
{
Int i;
If(p < 0 || p > L.lenght-1)
Return 0;
e = L.data[p];
for(i = p; i < L.lenght - 1; i++)
{
L.data[i] = L.data[i+1];
}
(L.lenght)--;
return 1;
}
2.单链表:
(1)结构体定义:
Typedef struct Lnode
{
Int data;
Struct Lnode *next;
}Lnode;
(2)归并算法:
Void merge (LNode *A, LNode *B, LNode *&C)
{
LNode *p = A;
LNode *q = B;
LNode *r ;
C = A;
C->next = NULL;
r = C;
Free(B);
While(p->next != NULL && q->next != NULL)
{
If(p->next->data < q->next->data)
r->next = p;
else
r->next = q;
r=r->next;
}
If(p->next == NULL)
r->next = q;
if(q->next == NULL)
r->next = p;
(3)头插法
Void createlistR (LNode *&C, int a[], int n)
{
}
3.双链表:
3.栈、队列和数组
栈和队列的基本概念
1.栈
栈是一种操作受限的线性表,是一种只能在一端进行插入或删除的线性表,能够进行插入或删除操作的一端称为栈顶top,插入和删除操作被称为入栈和出栈。
后进先出;顺序栈和链栈;n个元素出栈顺序Cn2n/(n+1)。
2.队列
是一种操作受限的线性表,仅允许在表的一端进行插入,在表的另一端进行删除。可插入的一端为队尾,可删除的一端为队头。插入是进队,删除是出队。
先进先出;顺序队;链队。
栈和队列的顺序存储结构
1.顺序栈
(1)定义:
(2)要素:
栈空:st.top == -1
栈满:st.top == maxsize - 1
上溢:栈满后继续入栈。
下溢:栈空后继续出栈。
进栈:++st.top;st.data[st.top] = x;或stack[++top] = x
出栈:x = st.data[st.top];--st.top;或x = stack[top--]
(3)代码:
初始化:(只需要将栈顶指针置为-1)
判断栈空:
进栈代码:(栈满不可进栈)
出栈代码:(栈空不可出栈)
2.顺序队(循环队列)
(1)定义:
Rear指向刚进队的元素位置;front指向刚出队元素的位置。进队的时候rear向后移,出队的时候front向后移,经过一系列操作后,两指针都会到达末端maxsize-1处,虽然队中已经没有了元素,但是仍然无法入队,这就是“假溢出”。要解决这种问题,可以把数组弄成一个环,让rear和front沿着环走,这样就产生了循环队列。
(2)要素:
队空:qu.rear == qu.front
队满:(qu.rear+1)%maxsize == qu.front
进队:qu.rear = (qu.rear+1)%maxsize;qu.data[qu.rear] = x;
出队:qu.front = (qu.front+1)%maxsize;x = qu.data[qu.front]
(3)代码:
初始化:
队空判断:
进队:
出队:
栈和队列的链式存储结构
1.链栈
(1)定义:
(2)要素:
栈空:lst->next == null
栈满:不存在栈满状态。
进栈(头插法):p->next = lst->next;lst->next = p
出栈(单链表删除):p = lst->next;x = p->data;lst->next = p->next;free(p)
(3)代码:
初始化:
栈空判断:
进栈:
出栈:
2.链队(第一个入队、最后一个出队的时候rear和front都要修改)
(1)定义:
(2)要素:
队空:lqu->rear == NULL或lqu->front == NULL
队满:不存在队满的情况
入队:lqu->rear->next = p;lqu->rear = p;
出队:q = lqu->front;lqu->front = q->next;x = q->data;free(q);
(3)代码
初始化:
队空判断:
入队:
出队:
栈和队列的应用
1.顺序栈应用
括号匹配、子程序调用、表达式求值、递归。
2.链栈应用
3.共享栈
提高内存的利用率,减少溢出的可能性。两个栈共享一片连续的存储空间,将两个栈的栈底分别设在这片内存空间的两端,这样当两个栈的栈顶在栈空间的某一位置相遇时,才产生上溢。
4.双端队列
是一种插入和删除操作在两端均可进行的线性表。可以看做是栈底连在一起的两个栈。
允许在一端进行插入删除,另一端只允许删除的双端队列称为输入受限的双端队列。允许在一端进行插入删除,另一端只允许插入的双端队列称为输出受限的双端队列。
特殊矩阵的压缩存储
1.KMP
2.特殊矩阵
对称矩阵:需要(1+n)n/2个存储空间。
A[n-1,0] = b[n(n-1)/2].
三角阵(上三角,下三角):需要(1+n)n/2+1个存储空间。
A[n-1,0] = b[n(n-1)/2],b[(1+n)n/2] = c
对角矩阵:第i行第一个元素为b[2+(i-2)*3]
3.稀疏矩阵
顺序存储:三元组表示法;伪地址表示法。
链式存储:邻接表表示法;十字链表表示法。
4.树与二叉树
树的基本概念
二叉树
1.二叉树的定义及其主要特征
(1)定义:
2.二叉树的顺序存储结构和链式存储结构
3.二叉树的遍历
4.线索二叉树的基本概念和构造
树、森林
1.树的存储结构
双亲存储结构(顺序存储结构):用一个整形的数组存储一棵树的信息,数组的下标表示树中的结点,数组元素存储该节点的双亲节点。(克鲁斯卡尔算法)
链式存储结构:孩子存储结构、孩子兄弟存储结构。
2.森林与二叉树的转换
3.树和森林的遍历
树与二叉树的应用
1.二叉排序树
(1)定义:
二叉排序树可以是空树,或者是符合以下性质的二叉树:
①若左子树不为空,则左子树中所有关键字的值均小于(或大于)根关键字的值;
②若有字数不为空,则有字数中所有关键字的值均大于(或小于)根关键字的值;
③左子树和右子树又各是一棵二叉排序树。
(2)存储结构
Typedef struct BTNode
{
Int key;
Struct BTNode *lchild;
Struct BTNode *rchild;
}BTNode;
(3)查找关键字算法
BTNode *BSTSearch(BTNode *bt,int key)
{
If(bt == NULL)
Return NULL;
If(bt->key == key)
Return bt;
Else if(key <bt->key)
return BSTSearch(bt->lchild, key);
Esle
return BSTSearch(bt->rchild, key);
}
(4)插入关键字算法
对于一个不存在于二叉排序树中的关键字,查找不成功的的位置就是该关键字的插入位置。
Int BSTInsert (BTNode *bt, int key)
{
If(bt == NULL)
{
bt = (BTNode*)malloc(sizeof(BTNode));
bt->key = key;
bt->lchild = NULL:
bt->rchild = NULL;
return 1;
}
Else
{
If(bt->key == key)
Return 0;
Else If(key < bt->key)
Return BSTInsert(bt->lchild, key);
Else
Return BSTInsert(bt->lchild, key);
}
}
(5)构建二叉排序树
Void CreateBST(BTNode *&bt, int key[], int n)
{
Int i;
bt = NULL;
for(i = 0; i < n; i++)
BSTInsert(bt, key[i]);
}
(6)删除关键字操作(不能把以该关键字结点为根的子树删除,而只删除这一个节点,并保留二叉排序树的特性)
①p结点为叶子结点。删除叶子结点不会影响二叉排序树特性,直接删除。
②p结点只有左子树而无右子树,或者只有右子树而无左子树。将p的子树直接连接在指向p的结点。
③p结点左右子树都有。先沿着p结点左子树的右指针一直往右走,直到来到最右面一个结点r(也可以沿着右子树的左指针一直往左走,只到最左面一个结点)。将p中的关键字用r中的关键字代替,若r是叶子结点,则按照①中的方法删除;若r不是叶子结点(r不可能同时有左右子树),则按照②中的方法删除。
2.平衡二叉树
(1)定义:AVL树,是一种特殊的二叉排序树。其左右子树全是平衡二叉树,且左右子树高度之差不超过1。
(2)平衡因子:左子树的高度减去右子树高度的差。平衡因子取值-1,0,1。
(3)平衡调整:首先找出插入节点后失去平衡的最小子树,然后调整这个子树,使之成为平衡树。
(4)操作:二叉平衡树建立、删除、平衡调整。
3.哈夫曼(Huffman) 树和哈夫曼编码
5.图
图的基本概念
图的存储及基本操作
``
1.邻接矩阵法
2.邻接表法
3.邻接多重表、十字链表
图的遍历
1.深度优先搜索
2.广度优先搜索
图的基本应用
1.最小(代价) 生成树
2.最短路径
3.拓扑排序
4.关键路径
6.查找
查找的基本概念
给定一个值k,在含有n条记录的表中查找记录k或关键字为k的记录,若找到,则成功,返回查找到的记录或者返回该记录的位置;若失败。则返回相应的指示信息。
ASL:平均查找长度(平均查找次数)
顺序查找法
分块查找法
折半查找法
名称 | 基本思想 | ASL1 | ASL2 | 时间复杂度 |
---|---|---|---|---|
顺序查找 | 从表的一端开始顺序扫描线性表,依次将扫描的关键字与给定的值k相比较,若当前扫描的关键字与k相等,则扫描成功,若扫描结束,仍未发现关键字与k相等的记录,则查找失败。 | (n+1)/2 | n | O(n) |
折半查找 | 首先确定该区间中间的位置mid=(low+high)/2,然后将待查k值与R[mid]相比较,若相等,则查找成功返回该位置;若不相等,则重新定位区间。若k>R[mid],则定位mid+1到high区间;若k<R[mid],则定位low到mid-1区间。递归处理所有新区间,直到区间长度小于1时结束。 | log2(n+1)-1 | O(log2n) | |
分块查找 | 把线性表分成若干块,每一块元素存储顺序是任意的,但是块与块之间必须按照关键字大小有序排列,即前一块的最大关键字要小于后一块中的最小关键字。建立索引表,储存每一块的最大关键字、开始位置和结束位置。 |
B-树及其基本操作、B+树的基本概念
1.B树
(1)B树中所有节点孩子结点个数的最大值称为B树的阶,用m表示。一棵B树可以是空树,也可以是符合以下条件的m叉树。
①每个节点最多有m个分支;最少分支数要看是否为根节点,若为根节点且不是叶子结点则至少有两个分支,非根非叶子结点至少有m/2向上取整个分支。
②n(k<=n<=m;根节点k=2;非根结点k=m/2向上取整)个分支的结点有n-1个关键字,并递增排序。
③节点内各关键字互不相等且单调递增。
④叶子结点处于同一层;可以用空指针表示,是查找失败的位置。
(2)基本操作
2.B+树概念
①在B+树中,具有n个关键字的结点具有n个分支;而B树中,具有n个关键字的结点有n+1个分支。
②在B+树中,每个结点中关键字个数n的取值范围为m/2<=n<=m,根节点的取值范围为2<=n<=m;而在B树中,它的取值范围分别是m/2-1<=n<=m-1和1<=n<=m-1。
③在B+树中,叶子结点包含信息,并且包含了全部关键字,叶子节点引出的指针指向记录。
④B+树中所有非叶子结点仅起一个索引作用,即该节点中只含有对应子树的最大关键字和指向改字数的指针,不含有该关键字对应记录的存储地址;而在B树中,每个关键字对应一个记录的储存地址。
⑤在B+树中,有一个指针p指向关键字最小的叶子结点,所有叶子结点连接成一个线性表,而B树没有。
散列(Hash) 表
1.概念:用给定的关键字来计算出关键字在表中的地址。
2.H(key)=key Mod 13求解过程(除留余数法,一般选择小于或等于表长的最大素数):
关键字 | 用hash函数计算地址 | 地址 |
---|---|---|
7 | 7 Mod 13 | 7 |
4 | 4 Mod 13 | 4 |
1 | 1 Mod 13 | 1 |
14 | 14 Mod 13 | 1 |
100 | 100 Mod 13 | 9 |
30 | 30 Mod 13 | 4 |
5 | 5 Mod 13 | 5 |
9 | 9 Mod 13 | 9 |
20 | 20 Mod 13 | 7 |
134 | 134 Mod 13 | 4 |
消除冲突后的Hash表(线性探查法):
地址 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
关键字 | 空 | 1 | 14 | 空 | 4 | 30 | 5 | 7 | 20 | 100 | 9 | 134 | |
关键字 | 1 | 14 | 4 | 30 | 5 | 7 | 20 | 100 | 9 | 134 | |||
比较次数 | 1 | 2 | 1 | 2 | 2 | 1 | 2 | 1 | 2 | 8 |
关键字比较次数(查找成功):
ASL1 = (1+2+1+2+2+1+2+1+2+8)/10=11/5
地址比较次数(查找不成功):
地址 | 由地址开始到空位置为止所要发生的比较操作的地址 | 比较次数 |
---|---|---|
0 | 0 | 1 |
1 | 1,2,3 | 3 |
2 | 2,3 | 2 |
3 | 3 | 1 |
4 | 4,5,6,7,8,9,10,11,12 | 9 |
5 | 5,6,7,8,9,10,11,12 | 8 |
6 | 6,7,8,9,10,11,12 | 7 |
7 | 7,8,9,10,11,12 | 6 |
8 | 8,9,10,11,12 | 5 |
9 | 9,10,11,12 | 4 |
10 | 10,11,12 | 3 |
11 | 11,12 | 2 |
12 | 12 | 1 |
3.常见的Hash函数的构造方法
直接定址法;数字分析法(r进制随机选取数字定址,如十进制);平方取中法;除留余数法。
4.常见的Hash冲突处理方法
开放定址法(线性探查法,平方探查法);链地址法。
解决冲突的方法 | 查找成功时ASL1 | 查找不成功时ASL2 |
---|---|---|
线性探查法(容易产生堆积) | [1+1/(1-a)]/2 | [1+1/(1-a)^2]/2 |
平方探查法(减少堆积,无法探查全部单元,只少探查一半的单元) | -(1/a)ln(1-a) | 1/(1-a) |
链地址法(没有堆积) | 1+a/2 | a+e^a=a(约等于) |
5.散列表性能分析
装填因子=关键字/表长度
链地址法;ASL1;ASL2。
字符串模式匹配
kmp
查找算法的分析及应用
7.排序
排序的基本概念
1.排序:将原本无序的序列重新排列成有序序列的过程。
2.稳定性:当前序列中有两个或两个以上相同的关键字,在排序前、排序后这些关键字的相对位置如果没有发生变化就是稳定,否则就是不稳定。
各种排序算法的比较
算法分类 | 算法 | 思想 | 流程 | 代码 | 时间复杂度 | 空间复杂度 |
---|---|---|---|---|---|---|
插入排序(在已知有序序列中,插入一个新的关键字) | 直接插入排序 | 每趟将一个待排序的关键字,按照其值大小插入到已经排好的部分有序序列适当的位置上,直到所有的待排序列关键字都被插入到有序序列为止。 | O(n^2) | O(1) | ||
折半排序 | 和直接插入排序类似,区别是查找插入位置的方法不同,折半插入排序是采用折半查找法来查找插入位置。基本条件是已经有序。Low>high时结束。 | O(nlog2n) | O(1) | |||
希尔排序 | 缩小增量排序,将待排序列按照某种规则分成几个子序列,分别对几个子序列进行直接插入排序。(增量选取规则,希尔(n/2、n/4向下取整)、帕佩尔诺夫和斯塔舍维奇(2^k+1))增量最后一个值取1,且尽量没有除1以外的公因子。 | O(n2)、O(n1.5) | O(1) | |||
选择排序(核心选择,每次选出最大或最小的关键字与第一个或最后一个关键字交换) | 简单选择排序 | |||||
堆排序 | ||||||
交换排序(核心是交换,每次排序都会让一个关键字排到它最终位置上) | 起泡排序 | 冒泡排序,首先将第一个关键字和第二个关键字比较,若第一个关键字大,则二者交换,否则不交换;然后第二个关键字与第三个关键字比较,若第二个关键字大,则二者交换,否则不交换……一直按这种方法进行下去,最终最大的关键字交换到最后,一趟起泡排序完成。经过多糖这样的排序,最终是整个序列有序。 | O(n^2) | O(1) | ||
快速排序 | 每趟选择当前所有子序列中的一个关键字(通常是第一个)作为枢轴,将子序列中比枢轴小的放在枢轴前面,比枢轴大的放在枢轴后面;当本趟所有的子序列划分完毕后,会得到新的更短的子序列,他们将成为下一趟划分的初始序列。(每一趟排序完都有一个关键字到达最终位置) | O(nlog2n) | O(log2n) | |||
二路归并排序 | ||||||
基数排序 |