目录
整体框架
基础概念
数据结构
[1] 数据结构是一门研究非数值计算的程序设计问题中计算机的操作对象间的逻辑结构和物理结构以及它们之间相互关系,并对这种结构定义和实现相应运算的学科。
[2] 基本概念
- 数据结构(Data Structure) :是相互之间存在一种或多种特定关系的数据元素的集合。
- 数据(Data) :指所有能输入到计算机中并被计算机程序加工处理的符号的总称。不仅包括数字、字符串,还包括图形、图像、声音、动画、视频等能通过编码而被加工的数据形式。
- 数据对象(Data Object) :是性质相同的数据元素的集合,是数据的一个子集。
- 数据元素(Data Element) :是数据的基本单位,数据集合中的元素。
- 数据项(Data Item) :是数据的不可分割的最小单位。一个数据元素可由若干个数据项组成。
[3] 数据结构的三要素
算法
[1] 算法选择策略:对于反复使用的算法应选择运行时间短的算法;而使用次数少的算法可力求简明、易于编写和调试;对于处理的数据量较大的算法可从如何节省空间的角度考虑。
[2] 程序运行时间影响因素
- 算法策略
- 问题规模
- 语言层次
- 编译程序所产生的机器代码的质量
- 机器执行指令的速度
[3] 时间复杂度:
O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n3) < O(2n) < O(n!) < O(nn)
线性结构
线性表
[1] 定义
[2] 线性表的存储
顺序存储-顺序表
链式存储-链表(单链表、双向链表、循环链表)
[3] 运算
增加、删除、修改、查找
[4] 优缺点对比
[5] 带头结点的单链表的优点
[6] 线性表的应用举例
一元多项式的表示及相加
约瑟夫环(Joseph Circle)
栈
[1] 定义
栈是仅限定在表尾进行插入和删除操作的线性表。
[2] *特点与术语
栈的修改是按后进先出的原则进行的,因此,栈称为后进先出表(LIFO)。
栈顶(top)--栈的表尾
栈底(bottom) --栈的表头
空栈--没有元素的栈
入栈(push) --向栈顶压入元素
出栈(pop) --从栈顶弹出元素
[3] 栈的存储结构
顺序栈--栈的顺序存储结构
链栈--栈的链式存储
[4] 栈的应用举例
数制转换(不同进制之间的转换)
括号匹配(…[…{…[…}…]…]…[…]…(…)…)…)
迷宫问题
函数的嵌套和递归调用
队列
[1] 定义
队列是仅限定在表尾进行插入和表头进行删除操作的线性表
[2] *特点与术语
队列的修改是按先进先出的原则进行的,因此,队列称为先进先出表(FIFO)。
队头(front)--队列的表头,即只允许删除的一端。
队尾(rear) --队列的表尾,即只允许插入的一端。
入队(EnQueue) --向队尾插入元素。
出队(DeQueue) --从队头删除元素。
[3]队列的存储结构
链队列--队列的链式存储结构
循环队列--队列的顺序存储结构
双端队列 - - 两头都可以进行入队和出队操作
[4] 优先队列
优先队列的问题是如何找到一种实现优先的方法,使得入队和出队列操作得以相对容易实现。
[5] 队列的应用举例
离散事件模拟
排队问题
作业控制
广度优先搜索
串
[1] 定义
串(String)是零个或多个字符组成的有限序列,一般记作:S=“a1a2a3…an” , ai(1≤i≤n)可以是字母、数字或其它字符。
[2] 基本术语
串的长度:串中所包含的字符个数;长度为零的串称为空串(Empty String),它不包含任何字符。
空白串:通常将仅由一个或多个空格组成的串称为空白串(Blank String)。
串的子串:串中任意个连续字符组成的子序列称为该串的子串(SubString),包含子串的串相应地称为主串。通常将子串在主串中首次出现时的该子串的首字符对应的主串中的序号,定义为子串在主串中的序号(或位置)。
[3] 串的存储结构
串的定长顺序存储
串的堆分配存储(动态存储分配的顺序表)
串的链式存储
串的模式匹配
[1] 概念定义
子串定位运算又称为模式匹配(Pattern Matching)或串匹配(String Matching)。在串匹配中,一般将主串称为目标串,子串称为模式串。若子串在主串中出现,则称匹配成功,子串出现的位置称为有效位移,否则称匹配不成功。模式匹配在文章的关键字查找中被广泛使用。
[2] 朴素的串匹配算法
匹配失败,从主串下一个字符开始匹配,暴力匹配,没有利用上一次匹配失败的信息来指导下一次的匹配过程。(回溯)
时间复杂度:O(n*m),n与m分别为主串和子串的长度
[3] KMP算法
算法思想
当主串的第i个字符与子串的第j个字符失配时,子串的前(j-1)个字符与主串的第i个字符前的(j-1)个字符已经比较过。若有主串的第i个字符前的(k-1)个字符与子串的前(k-1)个字符匹配,则只需主串的第i个字符与子串的第k个字符开始向后比较即可,i不必回溯。
简而言之:若子串中,‘p1……pk-1’=‘pj-k+1……pj-1’,则在 j 处的失配,i 不回溯,直接和子串的 k 位置比较即可。
非线性结构
数组和广义表
[1] 定义和特点
1、结构中的元素本身可以是具有某种结构的数据,但属于同一数据类型,比如:一维数组可以看作一个线性表,二维数组可以看作“数据元素是一维数组”的一维数组,三维数组可以看作“数据元素是二维数组”的一维数组,依此类推。
2、数组元素的存放地址是其下标的线性函数,所以数组中的任一元素可以在相同的时间内存取,即顺序存储的数组是一个随机存取结构。
[2] 数组的顺序表示
数组一旦建立,结构中的元素个数和元素间的关系就不再发生变化。因此,一般采用顺序存储的方法来表示数组。由于计算机的内存结构是一维的,因此用一维内存来表示多维数组,就必须按某种次序将数组元素排成一列序列,然后将这个线性序列存放在存储器中
行优先顺序或以行为主序存储方式
(多维数组,先排最右的下标,从右到左,最后排最左下标)
LOC(aij)=LOC(a11)+[(i-1)*n+j-1]*d
列优先顺序或以列为主序存储方式
(多维数组,先排最左下标,从右向左,最后排最左下标)
LOC(aij)=LOC(a11)+[(j-1)*m+i-1]*d
[3] 矩阵的压缩存储
压缩存储:为多个值相同的非零元素只分配一个存储空间;对零元素不分配空间。
特殊矩阵:非零元素按照一定的规律分布,常见的有对称矩阵、三角矩阵、对角矩阵等,一般都能找到矩阵中的元素与一维数组元素的对应关系,通过这个关系,仍能对矩阵的元素进行随机存取。(压缩思想:建立矩阵元素下标 i 与 j 与一维数组下标 k 之间的关系,则可以一一对应放入元素)
对称矩阵:
下三角矩阵(上只需对换 i 与 j ):
对角矩阵
稀疏矩阵:矩阵A中有s个非零元素,若s远远小于矩阵元素的总数(即s<<m×n),则称A为稀疏矩阵。设在矩阵A中,有s个非零元素,令 e=s/(m*n),称e为矩阵的稀疏因子,通常认为e<=0.05=1/20时称之为稀疏矩阵。存储稀疏矩阵时,由于非零元素的分布一般是没有规律的,因此在存储非零元素的同时,还必须同时记下它所在的行和列的位置(i,j),因此可由表示非零元的三元组及其行列数唯一确定(i,j,aij)。
三元组顺序表上的转置
[4] 广义表
定义:广义表是线性表的推广和多层次的线性结构
,L=(a1,a2,…,an ),n≥0,ai可以是单元素,也可以是一个表。
结构特点:
广义表的存储结构
广义表的应用举例
树
图
应用数据结构
搜索
排序
[1] 定义
将一个数据元素的任意序列重新排列成一个按关键字有序的序列。假设n个元素的序列{R1,R2,…,Rn},相应的关键字序列为{k1, k2,…, kn},确定1,2,…,n的一种排列p1,p2,…,pn ,使相应的关键字满足非递减(或非递增)关系kp1,kp2,…,kpn,从而得到一个按关键字有序的序列,这样的一个操作过程就是排序。
[2] 排序方式是否稳定
若ki=kj,且在排序前的序列中Ri领先于Rj,排序后Ri仍领先于Rj,则称所用的排序方法是稳定的;反之,若可能使排序后的序列中Rj领先于Ri ,则称所用的排序方法是不稳定的。
[3] 排序方法的度量
排序的时间复杂性以算法执行中的数据比较次数及数据移动次数来衡量,
此外,针对一种排序方法,不仅要分析它的时间复杂性,而且要分析它的空间复杂性、稳定性和简单性等。
[4] 内部排序和外部排序
1、待排序的记录存放在计算机的内存中所进行的排序操作称为内部排序。
2、待排序的记录数量很大,以致内存一次不能容纳全部记录,在排序过程中需要访问外存的排序过程称为外部排序。
排序方法汇总
排序算法名字即是该算法的特点
插入排序
直接插入排序
[1] 基本思想
n个待排序的元素由一个有序表和一个无序表组成,开始时有序表中只包含一个元素,排序过程中,每次从无序表中取出第一个元素,将其 插入 到有序表中的适当位置,使有序表的长度不断加长,完成排序过程。(插入的实现就是元素位置的移动,找到合适位置以后就赋值)
[2] 代码实现
void InsertSort(SqList &L) {
for(i = 2; i <= L.length; i++)
if (L.r[i].key < L.r[i-1].key) {
L.r[0] = L.r[i]; L.r[i] = L.r[i-1];
for(j = i - 2; L.r[0].key < L.r[j].key; j--)
L.r[j+1] = L.r[j];
L.r[j+1] = L.r[0];
}//if
[3] 时间复杂度
O(n2)
折半插入排序
在寻找插入位置时采用二分查找(一个元素在有序表中找合适的位置)
void InsertSort(SqList &L) /* 对顺序表L作折半插入排序 */
{
for(i=2;i <= L.length; i++)
{ L.r[0] = L.r[i]; /* 保存待插入元素 */
/*找插入的合适位置*/
low = 1;high = i-1; /* 设置初始区间 */
while(low <= high) /* 确定插入位置 */
{ mid = (low+high)/2;
if (L.r[0].key > L.r[mid].key)
low = mid+1; /* 插入位置在高半区中 */
else high = mid-1; /* 插入位置在低半区中 */
}/* while */
/*合适位置后面的元素右移一位*/
for(j = i - 1; j >= high +1; j--) /* high+1为插入位置 */
L.r[j+1] = L.r[j]; /* 后移元素,留出插入空位 */
L.r[high+1] = L.r[0]; /* 将元素插入 */
}/* for */
}/* InsertSort */
希尔排序(缩小增量排序)
[1] 改进的原理
元素基本有序时,直接插入排序的时间复杂度接近于O(n);元素数目 n 较少时,直接插入排序的效率较高。
[2] 算法思想
先将整个待排序元素序列分割成若干个子序列(由相隔“增量”个元素组成的),分别进行直接插入排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。(d=1)
[3] 代码实现
void ShellInsert(SqList &L,int dk) {//对顺序表L作一趟希尔排序
for(i = dk+1 i <= L.ength; i++)
if (L.r[i].key < L.r[i-dk].key) {//需将L.r[i]插入有序增量子表
L.r[0] = L.r[i]; //L.r[i]暂存入L.r[0]
for(j = i - dk; j>0 &&L.r[0].key < L.r[j].key; j -= dk)
L.r[j+dk] = L.r[j]; //寻找插入位置时记录后移
L.r[j+dk] = L.r[0]; //插入
}//if
} //ShellInsertSort
void ShellSort(SqList &L,int dlta[],int t) {
//按增量序列dlta[0..t-1]进行希尔排序
for(k = 0; k < t; k++)
ShellInsert(L,dlta[k]); //一趟增量为dlta[k]的希尔排序
} //ShellInsertSort
[4] 时间复杂度
希尔排序的分析是一个复杂问题,增量序列的设置是关键,尚没有正式的依据说明如何设置最佳的增量序列,大量的实验表明希尔排序所需的比较和移动次数可达到 n1.3,是不稳定的排序方法。
交换排序
冒泡排序
[1] 算法思想
将相邻位置的关键字进行比较,若为逆序则交换之,依次类推到最后一个元素,为一趟冒泡排序。
[2] 代码实现
//冒泡排序算法
public static void bubbleSort(int a[])
{
int i,j,t,n=a.length;
for(i=n-1;i>=1;i--){ // i表示冒泡排序的轮次,或者最右边有序元素的个数
for(j=0;j<i;j++) //对无序表a[0:i]进行两两比较和交换
if(a[j]>a[j+1]){
t=a[j];
a[j]=a[j+1];
a[j+1]=t;
}
}
}
[3] 时间复杂度
O(n2)
快速排序
[1] 算法思想
取待排序序列中的某个元素作为基准(一般取第一个元素),通过一趟排序,将待排元素分为左右两个子序列,左子序列元素的关键字均小于或等于基准元素的关键字,右子序列的关键字则大于基准元素的关键字,然后分别对两个子序列继续进行排序,直至整个序列有序,为不稳定的排序方法
(在一个正序的序列中,任意一个元素大于等于左边的元素,小于或等于右边的元素,快排利用这种思想层层递归,即可达到任意元素有序,现象–>算法–>现象)
[2] 改进的由来
快速排序是对冒泡排序的一种改进方法,算法中元素的比较和交换是从两端向中间进行的,排序码较大的元素一次就能够交换到后面的单元,排序码较小的记录一次就能够交换到前面的单元,因而总的比较和移动次数较少。
[3] 代码实现
元素交换的方式
int Partition(SqList &L,int low,int high) {
pivotkey = L.r[low].key; i = low; j = high;
while (i<j) {
while (i<j && L.r[j].key >= pivotkey)
j--; /*从后往前寻找比枢轴元素小者*/
L.r[i] ←→L.r[j]; /*比枢轴元素小者交换到前半区间*/
while (i<j && L.r[i].key <= pivotkey)
i++; /*从前往后寻找比枢轴元素大者*/
L.r[j] ←→L.r[i]; /*比枢轴元素大者交换到后半区间*/
}
return i; /*返回枢轴元素的位置*/
}//Partition
void QSort(SqList &L, int low, int high) {
//对L.r[low]~L.r[high]的元素进行快速排序
if (low < high) {
pivotloc = Partition(L,low,high); //一趟划分
QSort(L,low,pivotloc - 1);
QSort(L, pivotloc+1,high);
}//if
} //QSort
元素移动的方式
int Partition(SqList &L,int low,int high) {
L.r[0] = L.r[low]; pivotkey = L.r[0].key; i = low; j = high;
while (i<j) {
while (i<j && L.r[j].key >= pivotkey) j--;
L.r[i] = L.r[j];
while (i<j && L.r[i].key <= pivotkey) i++;
L.r[j] = L.r[i];
}
L.r[i] = L.r[0]; return i;
}//Partition
void QSort(SqList &L, int low, int high) {
//对L.r[low]~L.r[high]的元素进行快速排序
if (low < high) {
pivotloc = Partition(L,low,high); //一趟划分
QSort(L,low,pivotloc - 1);
QSort(L, pivotloc+1,high);
}//if
} //QSort
[4] 时间复杂度
快速排序在最好情形下(左、右子区间的长度大致相等),则结点数n与二叉树深度h应满足log2n<h<log2n+1,所以总的比较次数不会超过(n+1)log2n。因此,快速排序的最好时间复杂度应为O(nlog2n),平均时间复杂度也为O(nlog2n),最坏时间复杂度为O(n2)。
[5] 空间复杂度
平均情况下:O(logn);在最坏情况下(逆序或正序),空间复杂度为O(n)
[6] 数学证明
快速排序的主要时间是花费在划分上,对长度为k的记录序列进行划分时关键字的比较次数是k-1 。设长度为n的记录序列进行排序的比较次数为T(n) ,则:
最好情况:每次划分得到的子序列大致相等
最坏情况:最坏情况下,每一次划分,枢轴都处于标的左或右端点。
平均情况:在平均情况下,设基准位置是k(1≤k≤n)。
[7] 算法的改进
1、枢轴元素的选取:三者取中;随机选择
2、当划分出的子序列长度小于某个值时,不再递归,而进行直接插入排序
选择排序
简单选择排序
[1] 算法思想
第一趟在n个记录中选取最小记录作为有序序列的第一个记录,第二趟在n-1个记录中选取最小记录作为有序序列的第二个记录,第i趟在n-i+1个记录中选取最小的记录作为有序序列多中的第i个记录。(找到最小元素所在的下标,和前面的元素交换位置)
[2] 代码实现
void SelectSort(SqList &L) {//对顺序表作简单选择排序
for(i = 1; i < L.length; i++) {
for(k = i+1, j = i; k <= L.length; k++)
if (L.r[k].key < L.r[j].key) j = k;
if (j != i) {L.r[i] ← → L.r[j];}
} //for
} // SelectSort
[3] 时间复杂度
O(n2)
树形选择排序(锦标赛排序)
[1] 算法思想
1、首先对n个记录的关键字两两进行比较,然后在n/2个较小者之间再进行两两比较,如此重复,直至选出最小关键字的记录。
2、选出最小记录后,将树中的该最小记录修改为无穷大,然后从该叶子结点所在子树开始修改到达树根的路径上的结点。
3、以后每选出一个小元素,都只需进行(logn)次比较。
[2] 算法缺陷
1、需要较多的辅助空间
2、存在与“无穷大”进行比较的冗余比较
堆排序
[1] 堆的定义
对于n个元素的序列{k1,k2,…,kn},当且仅当满足以下关系时,称之为堆。
[2] 算法思想
对一组待排序记录的关键字,首先把它们按堆的定义建成小(大)顶堆,然后输出堆顶的最小(大)关键字所代表的记录,再对剩余的关键字建堆,以便得到**次小(大)**的关键字,如此反复进行,直到全部关键字排成有序序列为止。(建堆<–>调整)
[3] 建堆
[4] 调整
输出堆顶元素后,将剩余元素重新调整为一个新堆