数据结构复习笔记

数据结构

一、图的相关概念和题型

*完全有向无向图

有向图:被指——入度;指向——出度。

无向图:边数(E)=0~n(n-1)/2;无向完全图E=n(n-1)/2;

有向图:边数(E)=0~n(n-1);有向完全图E=n(n-1)。

*图的邻接矩阵储存(无向图&有向网)

无向图:0/1表示,0为不连接,1为连接。

有向网:邻接矩阵元素为两节点的权值,自己与自己为∞。

*无向图的深度/广度优先遍历序列

(1)深度优先遍历:

从根结点a开始向下遍历(遍历其未访问邻接点),若顶点存在未访问邻接点则一直向下遍历直到没有未访问邻接点,然后回溯至上一个结点。

一直向下,直到没有未访问邻接点。

(2)广度优先遍历:

从根结点a开始遍历(遍历其未访问邻接点),依次访问a的所有的未访问邻接点,一行完了再下一行。

横着访问,优先遍及所有邻接点。

*最小生成树问题

最后有n个点n-1条边

普利姆算法(加点法)

选一起点,每次加入距离已有节点权值最小的节点加入(n对n种选择一个最小权值)

注意:

(1)每次都选取权值最小的边,但不能构成回路,构成环路的边则舍弃。
(2)遇到权值相等,又均不构成回路的边,随意选择哪一条,均不影响生成树结果。由一个连通图构造的最小生成树可能不唯一,但权值之和是唯一的。

克鲁斯卡尔算法(加边法)

将所有边按照权值的大小进行升序排序,然后从小到大一一判断,条件为:如果这个边不会与之前选择的所有边组成回路,就可以作为最小生成树的一部分;反之,舍去。直到具有 n 个顶点的连通网筛选出来 n-1 条边为止

*AOE-网&拓扑排序&关键路径问题

**AOE-网:**顶点——活动,弧——活动间的优先关系,有向无环图。

拓扑排序:

(1)每个顶点出现且只出现一次。
(2)若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面。

img

步骤:

  1. 从 DAG 图中选择一个 没有前驱(即入度为0)的顶点并输出。

  2. 从图中删除该顶点和所有以它为起点的有向边。

  3. 重复 1 和 2 直到当前的 DAG 图为空或当前图中不存在无前驱的顶点为止。后一种情况说明有向图中必然存在环。

    img

    拓扑序列: { 1, 2, 4, 3, 5 }。

关键路径问题:关键活动&至少所需时间

  • 关键路径:起点至终点最长的路径。关键路径上的活动均为关键活动。关键路径不唯一(两条路径长度相等都为max)

  • 松弛时间:最早开始时间与最迟开始时间之差,或者最早结束时间与最迟结束时间之差。

    (路径表示所需要完成的活动,权值为时间;点表示已完成之前的活动,可以开始之后的活动)

(1)完成整项工程至少需要的时间——关键路径(最长路径)的长度和

(2)关键活动:关键路径上的活动均为关键活动。

*最短路径问题(迪杰斯特拉算法&佛洛依德算法)

二、查找

*顺序查找

对于任意一个序列以及一个给定的元素,将给定元素与序列中元素依次比较,直到找出与给定关键字相同的元素,或者将序列中的元素与其都比较完为止。对于没有排序的数据,只能使用顺序查找;如果数据已经排好序,可以使用速度比较快的折半查找(二分查找)。

typedef int KeyType;
typedef char InfoType;

typedef struct
{
KeyType key;
InfoType info;
}Key;

void Search_Seq01(Key *K, KeyType key, int len)//顺序查找 { //每次循环都需要两次判断(i<len)和(K[i].key == key) //故判断总次数为2*len次
for(int i = 1; i < len; i++)
if(K[i].key == key)
{
printf("关键字信息为:%c\n", K[i].info);
break;
}
}
void Search_Seq02(Key *K, KeyType key, int len)//设置监视哨查找
{
//占用第一个数组空间存储需要查找的数据
//每次循环只需要一次判断(K[i].key != key)
//故总共需要len次判断,相较顺序查找更快
K[0].key = key;
for(int i = len; K[i].key != key; i--);
printf("关键字信息为:%c\n", K[i].info);
}

*折半查找(二分查找)

条件:顺序储存结构;必须按关键字大小有序排列

int BinSrch(RecordList l,KeyType k)

{

low=1;high=l.length;

while(low<=high)

{

mid=(low+high)/2;

if(k==l.r[mid].key)

return (mid);

else if(k<l.r[mid].key)

high=mid-1;

else

low=mid+1;

}

return 0;

}

*二叉排序树

二叉排序树(Binary Sort Tree)或者是一颗空树;或者是具有如下性质的二叉树:

(1) 若它的左子树不空,则 左子树 上所有结点的值 均小于 它的根结点的值;

(2) 若它的右子树不空,则 右子树 上所有结点的值 均大于 它的根结点的值;

(3) 它的 左、右子树又分别为二叉排序树 。

​ 5

​ 2 6

​ 1 4 ∧ 8

已知关键字序列,建立二叉排列树:

顺序插入,一次比较,比当前结点小则插入左,大则插入右。

*平均查找长度(ASL)

在这里插入图片描述

ASL=1/n*比较次数之和

其中n为查找表中元素个数,Pi为查找第i个元素的概率,通常假设每个元素查找概率相同,Pi=1/n,Ci是找到第i个元素的比较次数。

一个算法的ASL越大,说明时间性能差,反之,时间性能好。

顺序查找中ASL计算:

在这里插入图片描述

在这里插入图片描述

折半(二分)查找中ASL计算:

例:给11个数据元素有序表(2,3,10,15,20,25,28,29,30,35,40)采用折半查找。则ASL成功和不成功分别是多少?

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

*哈希法查找(散列)

(1)散列函数构造:

直接定址法

数字分析法

平方取中法

除留取余法(重点)

(2)散列表构造:

拉链法处理冲突:

按公式全部求余确定位置,关键字个数为下表总数(0开始),下表对应位置无数据则为空,余数相同则通过链式链接再后面。

ASL计算

ASL(s)=1/n*(成功查找的次数和) ——1次、2次、3次

ASL(us)=1/(n+1)*(空查找的次数和) ——未储存的位置为一次,之后位置递推+1次

拉链法

开放地址法处理冲突:

平方探测法

再散列法

伪随机序列法

线性探测法(线性探测再散列)(二次探测再散列)

按公式全部求余填入线性表(下表从零开始),若余数所对应位置已存在元素则往后平移一个位置。

在这里插入图片描述

ASL计算

在这里插入图片描述

在这里插入图片描述

顺序查找&折半查找&分块查找优缺点:

顺序查找:

1)缺点:查找效率较低,特别是当待查找集合中元素较多时,不推荐使用顺序查找。
2)优点:算法简单而且使用面广,
a)对表中记录的存储没有任何要求,顺序存储和链接存储均可。
b)对表中记录的有序性也没有要求,无论记录是否按关键码有序均可

折半查找:

优点:比较次数少,查找速度快,平均性能好;

缺点:要求待查表为有序排列,且插入删除困难。

分块查找:

优点 :插入删除比较容易,无需进行大量移动。

缺点:要增加一个索引表的存储空间并对初始索引表进行排序运算。

三、排序

*插入类排序:直接插入排序&折半插入排序&希尔排序

直接插入排序:

默认第一个元素有序,每一趟把后一个元素与前面已经有序的元素进行比较放到适合的位置使数列有序。

img

void insertSort(int R[],int n) //待排关键字存储在R[]中,个数为n
{
int i,j;
int temp;
for(i=0;i<n;i++)
{
temp=R[i];
j=i-1;
while(j>=0&&temp<R[j])
{
R[j+1]=R[j]; // 将原来位置上的元素向后顺移
--j;
}
R[j+1]=temp; // 找到插入位置,将temp中暂存的待排关键字插入
}
}

折半插入排序:

以从小到大排序为例,首先用key存储需要排序的数据
第一步:折半查找——用low、mid、high划分两个区域【low,mid-1】和【mid+1,high】
第二步:判断——如果key值小于序列的中间值【mid】,则代表key值应该插入左边的区域【low,mid-1】,然后对【low,mid-1】再重复划分区域,直到low>high为止
第三步:插入——最后的插入位置应该是high+1,我们只需要先将high之后位置的数据整体后移,然后将key赋值给【mid+1】,即完成插入。

在这里插入图片描述

void BInsertSort(int num[],int N)
{
int i,j,low,high,mid,key;
for(i=1;i<N;i++)
{
key=num[i];
low=0;
high=i-1;
while(low<=high)//折半查找
{
mid=(low+high)/2;
if(num[mid]>=key)
high=mid-1;//如果大于key值,则查找范围缩小到左子序列
else
low=mid+1;//如果小于key值,则查找范围缩小到右子序列
}
for(j=i-1;j>=high+1;j--)
num[j+1]=num[j];//将high之后的数据整体后移
num[high+1]=key;//将key值插入该位置
}
}

希尔排序(缩小问题规模使用直接插入排序):

1.先选定一个小于N的整数gap作为第一增量,然后将所有距离为gap的元素分在同一组,并对每一组的元素进行直接插入排序。然后再取一个比第一增量小的整数作为第二增量,重复上述操作…
2.当增量的大小减到1时,就相当于整个序列被分到一组,进行一次直接插入排序,排序完成。

在这里插入图片描述

//希尔排序
void ShellSort(int* arr, int n)
{
int gap = n;
while (gap>1)
{
//每次对gap折半操作
gap = gap / 2;
//单趟排序
for (int i = 0; i < n - gap; ++i)
{
int end = i;
int tem = arr[end + gap];
while (end >= 0)
{
if (tem < arr[end])
{
arr[end + gap] = arr[end];
end -= gap;
}
else
{
break;
}
}
arr[end + gap] = tem;
}
}
}

*交换类排序:冒泡排序&快排

冒泡排序:

多趟,每一趟将相邻元素比较交互,小在前,大在后

//冒泡排序
void BubbleSort(int* arr, int n)
{
int end = n;
while (end)
{
int flag = 0;
for (int i = 1; i < end; ++i)
{
if (arr[i - 1] > arr[i])
{
int tem = arr[i];
arr[i] = arr[i - 1];
arr[i - 1] = tem;
flag = 1;
}
}
if (flag == 0)
{
break;
}
--end;
}
}

快速排序:

(1)首先,在这个序列中随便找一个数作为基准数,通常为了方便,以第一个数作为基准数。
(2)设第一个元素和最后一个元素分别为哨兵i和哨兵j(下标),j先向左移寻找小于基准的树,i向右移寻找大于基准的树,找到后交换。重复进行直到基准数到最中间(有序)。
(3)然后,左边和右边的数据可以看成两组不同的部分,重复上述1和2步骤。当左右两部分都有序时,整个数据就完成了排序。

void Quick_Sort(int *arr, int begin, int end){
if(begin > end)
return;
int tmp = arr[begin];
int i = begin;
int j = end;
while(i != j){
while(arr[j] >= tmp && j > i)
j--;
while(arr[i] <= tmp && j > i)
i++;
if(j > i){
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
arr[begin] = arr[i];
arr[i] = tmp;
Quick_Sort(arr, begin, i-1);
Quick_Sort(arr, i+1, end);
}

*选择类排序:简单选择排序

(1)第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置。
(2)然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。
(3)以此类推,直到全部待排序的数据元素的个数为零。

每一趟选出最小元素放到最前。

void SelectSort(int A[],int n){
for(int i=1;i<=n;i++){
int min=i;
for(int j=i+1;j<=n;j++){
if(A[j]<A[min]){
min=j;
}
}
if(min!=i){
int t=A[i];
A[i]=A[min];
A[min]=t;
}

}

}

*归并排序(分治思想):

先两两分组进行比较,小在前大在后;之前的分组两两合并再进行排序;重复直到有序。

四、树&二叉树

*已知先序遍历、中序遍历后续遍历画出二叉树

先序遍历:根左右

中序遍历:左根右

后续遍历:左右根

已知
先序遍历(根左右)为:ABDCEGF
中序遍历(左根右)为:BDAEGCF

(1)先序遍历第一个元素A为根节点,观察中序遍历A的左边元素对应二叉树中在A的左分支,右边同理。
该题根节点A的左边有B D两个元素,右边有E G C F。再结合根左右的顺序可得到B为A的左孩子结点,
C为A的右孩子结点。E为C的子结点,但未知左右。
(2)根据中序遍历左根右的顺序,B没有左孩子结点,D为B的右孩子结点。
(3)因为中序顺序为左根右,结合(1)所以F为C的右孩子结点(无后继),E为左结点。
(4)根据中序左根右顺序EGC可得,E无左结点,G为E的右孩子结点。

已知
先序遍历(根左右)为:ABDCEGF
后序遍历(左右根)为:DBGEFCA

(1)根据先序遍历特点可得出:A为根节点,B为A的左孩子结点。根据后序遍历特点可得:C为A的右孩子
结点,D为B的孩子结点。
(2)根据(1)再结合先序遍历得:B D在A的左分支,E G F在右分支,E为C的孩子结点。
(3)根据(2)再结合后序遍历(左右根)得:E为C左结点,F为C右结点。
(4)根据(3)和先序遍历可得:G是E的子结点。但无法得知D G分别是 B E的左还是右结点。此时二叉树不唯一。

已知
中序(左根右):BGCAEHFD
后序(左右根):GBCEHDFA
(1)根据后序遍历特点得:A为根节点,F为A的右孩子结点。根据中序得:B G C在A的左分支,E H F D在右分支。
(2)结合(1)与后序遍历:C为A的左结点。D为F的子结点。再根据中序得:D为F的右孩子结点。
(3)根据(2)和左右根:H为F的左结点,再根据左根右:E为H的左结点。
(4)根据左根右得:B G 在C 的左分支,C无右结点,再根据左右根:B为G的父结点,再根据左根右:G是B的右结点。

*树转换成二叉树

同一根下的相邻兄弟之间加线,删掉第一个之后的结点与根间的线

*已知叶子结点权值,构造哈夫曼树,并求带权路径长度WPL

(1)对这一组数字从小到大进行排序。

(2)选择两个最小的数字(哈夫曼树是从下往上排列的)

用一个类似于树杈的“树枝”连接上两个最小的数。在顶点处计算出这两个数字的和 并写在上面。然后再比较剩下的数字和这个和的大小,再取出两个最小的数字进行排列

(3)若所求和大于后面两个树,则用后面两个树重新开一个分支

(4)将分支合并

WPL=结点权值*距离段数(到根)之和

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值