考完啦!
一、基本概念
数据结构是相互之间存在一种或多种特定关系的数据元素的集合;包括三个方面:逻辑结构、存储结构和数据的运算
数据的逻辑结构和存储结构是密不可分的两个方面,一个算法的设计取决于所选定的逻辑结构,而算法的实现依赖于所采用的存储结构
数据的逻辑结构分为线性结构和非线性结构;线性表是典型的线性结构;集合、树、图是典型的非线性结构
数据的存储结构(物理结构):主要有顺序存储、链式存储、索引存储和散列存储;
顺序存储:把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来体现;优点是可以实现随机存取,每个元素占用最少的存储空间;缺点是只能使用相邻的一整块存储单元,因此可能产生较多的外部碎片
链式存储:不要求逻辑上相邻的元素在物理位置上也相邻,借助指示元素存储地址的指针来表示元素之间的逻辑关系;优点是不会出现碎片现象,能充分利用所有存储单元;缺点是每个元素因存储指针而占有额外的存储空间,且只能实现顺序存取
散列存储:根据元素的关键字直接计算出该元素的存储地址,又称哈希存储;优点是检索、增加和删除结点的操作都很快;缺点是若散列函数不好,则可能出现元素存储单元的冲突,而解决冲突会增加时间和空间开销
算法的特性:有穷性、确定性、可行性、输入、输出
算法的目标:正确性、可读性、健壮性、效率和低存储量需求
时间复杂度:1<logn<n<nlogn<n2<n3<2n<n!<nn
顺序表:逻辑上相邻的元素,对应的物理存储位置也相邻;特点是随机访问,可在O(1)时间内找到指定元素;存储密度高,每个结点只存储数据元素;插入和删除操作需要移动大量元素
链表:插入和删除操作不需要移动元素,只需要修改指针;只能顺序存取,不支持随机存取
头结点与头指针:在线性表的链式存储结构中,头指针指链表的指针,若链表有头结点则是链表的头结点的指针,头指针具有标识作用,故常用头指针冠以链表的名字;头结点是为了操作的统一、方便而设立的,放在第一元素结点之前,其数据域一般无意义,有头结点后,对在第一元素结点前插入结点和删除第一结点,其操作与对其它结点的操作统一,而且无论链表是否为空,头指针均不为空;首元结点也就是第一元素结点,它是头结点后边的第一个结点
栈:是只允许在一端进行插入或删除操作的线性表;特性为先入先出
队列:是一种操作受限的线性表,只允许在表的一端进行插入,而在表的另一端进行删除;特性是先进先出
栈和队列的比较:相同点,都是操作受限的线性表,都有顺序和链式两种存储结构;不同点,栈只能在一端进行插入和删除操作,其中允许进行插入和删除操作的一端称为栈顶,它是动态变化的,另一端称为栈底,它是固定不变的,特点是先进后出;队列是允许在表的一端进行插入,表的另一端进行删除,进行插入的一端叫队尾,进行删除的一端叫队头,特点是先进先出
递归:通常把一个大型的复杂问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的代码就可以描述出解题过程所需要的多次重复计算,大大减少了程序的代码量,但它的效率并不高;精髓在于能否将原始问题转换为属性相同但规模较小的问题;将递归算法转换为非递归算法,通常需要借助栈来实现这种转换
队列的应用:主机和打印机之间速度不匹配,设置一个打印数据缓冲区,主机把要打印输出的数据依次写入这个缓冲区,打印机从缓冲区中按照先进先出的原则依次取出数据;解决由多用户引起的资源竞争问题
结点的度:树中一个结点的孩子个数
树的度:树中结点的最大度数
分支结点:度大于0的结点
叶子结点:度为0的结点
路径:树中两个结点之间的路径是由这两个结点之间所经过的结点序列构成的,路径长度是路径上所经过的边的个数;由于树中的分支是有向的,从双亲指向孩子,所以路径是从上向下的,同一个双亲的两个孩子之间不存在路径
二叉树的性质:叶子结点数等于度为2的结点数加1;非空二叉树上第k层上至多有2*(k-1)个结点;高度为h的二叉树至多有2*h-1个结点
连通图:至少n-1条边,至多n(n-1)/2条边
强连通图:至少n条边,至多n(n-1)条边
无向图全部顶点的度之和等于边数的2倍
有向图顶点的度等于入度与出度之和,全部顶点的入度之和与出度之和相等且等于边数
拓扑排序是对有向无环图的顶点的一种排序,它使得若存在一条从顶点A到顶点B的路径,则在排序中顶点B出现在顶点A的后面;每个AOV网都有一个或多个拓扑排序序列;当前网中不存在无前驱的顶点说明有向图中存在环,排序失败
B树:根结点至少有一个关键字,至多m-1个关键字;其他结点至少[m/2]-1个关键字,至多m-1个关键字;左右子树高度之差为0
散列函数:一个把查找表中的关键字映射成该关键字对应的地址的函数;常用的散列方法有直接定址法、除留余数法、数字分析法、平方取中法;处理冲突的方法:开放定址法、链表散列法;在开放定址的情形下,不能随便物理删除表中的已有元素,因为若删除元素,则会截断其他具有相同散列地址的元素的查找地址,因此要删除一个元素时,可以给它做一个删除标记进行逻辑删除,还需要定期维护散列表,要把删除标记的元素物理删除掉
装填因子:定义为一个表的装满程度,等于表中记录数/散列表长度
算法的稳定性:若待排序表中有两个元素,其对应的关键字相同,若使用某一排序算法排序后,其相对位置不发生变化,则称这个排序算法是稳定的
分而治之算法:分而治之方法与软件设计的模块化方法非常相似;为了解决一个大的问题,可以把它分成两个或多个更小的问题,分别解决每个小问题,把各个小问题的解答组合起来,即可得到原问题的解答;小问题通常与原问题相似,可以递归地使用分而治之策略来解决;原则:分而治之算法过程包括分解问题的部分、递归调用部分、合并处理部分
贪婪算法:逐步构造一个最优解,每一步都在一定的标准下,做出一个最优决策,决策一旦作出,就不可再更改,做出决策所依据的标准称为贪婪准则;贪婪准则:做决策的依据;举例:拓扑排序算法、单源最短路径的Dijkstra算法
快速排序算法思想:首先选取一个枢轴(一般选第一个元素),指针i和j分别在序列的开头和结尾,取出枢轴位置上的数;使用指针j从后往前扫,遇到比枢轴小的数停住,和指针i交换,使用指针i从前往后扫,遇到比枢轴大的数停住,和指针j交换,重复以上过程,直到i和j相遇,这个位置即为枢轴的最终位置;至此,枢轴左边的数都小于或等于它,右边的数都大于或等于它,然后对枢轴两边的数分别进行快速排序
快速排序的轴选择最佳方法:从序列的头尾以及中间选取三个元素,再取这三个元素的中间值作为最终的枢轴元素;或随机从当前表中选取枢轴元素
Kruskal算法:在将要并入顶点和边的时候,检查如果并入后,已并入顶点个数等于已并入边的个数,则存在回路,否则不存在回路
B树的用途:在文件系统、磁盘等直接存取设备上组织动态的查找表,作为数据库中存取和查找文件的索引
优先队列:是0个或多个元素的集合,每个元素都有一个优先权或值;当访问元素时,具有最高优先级的元素最先被删除,优先队列具有最高级先出的特点;若使用线性表描述优先队列,插入删除操作的平均时间复杂度都为On;若使用堆描述,则插入删除操作的平均时间复杂度为logn
二、基本逻辑结构
(一)顺序表
1.基本操作
InitList(&L)//初始化表;构造一个空的线性表
Length(L)//求表长;返回线性表L的元素个数
LocateElem(L,e)//按值查找;在表中查找具有给定关键字值的元素
GetElem(L,i)//按位查找;获取表中第i个位置的元素的值
ListInsert(&L,i,e)//插入操作;在表中第i个位置上插入指定元素e
ListDelete(&L,i,&e)//删除操作;删除表中第i个位置的元素,并用e返回删除元素的值
PrintList(L)//输出操作;按顺序输出线性表的所有元素值
Empty(L)//判空操作;若L为空表,返回true,否则返回false
DestroyList(&L)//销毁操作;销毁线性表,并释放表所占用的内存空间
2.顺序表的定义
//静态分配顺序表
#define MaxSize 50//表最大长度
typedef struct
{
int data[MaxSize];
int length;
}SqList;
//动态分配顺序表
#define InitSize 100//表最大长度
typedef struct
{
int *data;
//动态分配语句
//L.data=(int *)malloc(sizeof(int)*InitSize);
int MaxSize,length;//数组的最大容量和当前长度
}SeqList;
3.顺序表基本操作的实现
//动态分配初始化
void InitList(SeqList &L)
{
L.data=(int *)malloc(InitSize*sizeof(int));
L.length=0;
L.MaxSize=InitSize;
}
//增加动态数组的长度
void IncreaseSize(SeqList &L,int len)
{
int *p=L.data;
L.data=(int *)malloc((L.MaxSize+len)*sizeof(int));
for(int i=0;i<L.length;i++)
{
L.data[i]=p[i];//将数据复制到新区域
}
L.MaxSize=L.MaxSize+len;
free(p);
}
//插入操作
bool ListInsert(Sqlist &L,int i,int e)
{
if(i<1||i>L.length+1)//判断i的范围是否有效
return false;
if(L.length>=MaxSize)//存储空间已满
return false;
for(int j=L.length;j>=i;j--)//第i个元素及以后的元素后移
L.data[j]=L.data[j-1];
L.data[i-1]=e;
L.length++;
return true;
}
//删除操作
bool ListDelete(SqList &L,int i,int &e)
{
if(i<1||i>L.length)
return false;
e=L.data[i-1];//弹出被删除元素
for(int j=i;j<L.length;j++)//第i个位置后的元素前移
L.data[j-1]=L.data[j];
L.length--;
return true;
}
//按值查找
int LocateELem(SqList L,int e)
{
int i;
for(i=0;i<L.length;i++)
if(L.data[i]==e)
return i+1;
return 0;//退出循环,查找失败
}
//在顺序表中找到第一个值为X的元素,在该元素之前插入一个值为Y的元素,如果找不到X,则新元素为顺序表最后一个元素,插入成功返回1,否则返回0
int Insert_xy(SqList &L,int x,int y)
{
if(L.length>=MaxSize)
return 0;
int i,flag=0;
for(i=0;i<L.length;i++)
{
if(x==L.data[i])
{
flag=1;
break;
}
}
if(flag==0)
i=L.length;
for(int j=L.length-1;j>i-1;j--)
L.data[j+1]=L.data[j];
L.data[i]=y;
L.length++;
return 1;
}
//删除顺序表中关键字在X~Y之间的元素
//算法思想:扫描顺序表,记录要删除元素的个数,剩下的元素依次前移至指定位置
typedef struct SqList
{
Elemtype data[MaxSize];
int length;
}SqList;
void Delete(SqList &L,Elemtype x,Elemtype y)
{
int n=0;
for(int i=0;i<L.length;i++)
{
if(L.data[i]>=x&&L.data[i]<=y)//待删除元素后续会被覆盖或丢弃,故不需要特地处理
n++;
else//不需要删除的元素,前移n个位置
L.data[i-n]=L.data[i];
}
L.length=L.length-n;
}
//将数组从s到t范围内的所有奇数移到偶数之前,要求时间复杂度为n
//思路:设置首尾两个标记ij,从左往右、从右往左扫描数组,当同时遇到偶数和奇数时,交换元素,直至i=j
void Move(int &r[],int s,int t)
{
int i=s,j=t,temp;
while(i<j)
{
while(i<j&&r[i]%2==1)//若左边都是奇数,则继续往右找,直至遇到偶数
i++;
while(i<j&&r[j]%2==0)//若右边都是偶数,则继续往左找,直至遇到奇数
j—-;
if(i<j)//此时左边是偶数,右边是奇数,则交换元素
{
temp=r[i];
r[i]=r[j];
r[j]=temp;
}
}
}
(二)链表
1.基本操作
同顺序表
2.链表的定义
typedef struct LNode//单链表结点定义
{
int data;//数据域
struct LNode *next;//指针域
}LNode,*LinkList;//两者等价;前者强调是一个单链表,后者强调是一个结点
3.链表基本操作的实现
//头插法建立单链表
LinkList List_HeadInsert(LinkList &L)//得到逆向的单链表
{
LNode *s;//定义新结点
int x;//存放新结点的数据
L=(LinkList)malloc(sizeof(LNode));//创建头结点
L->next=NULL;//初始为空链表
scanf("%d",&x);
while(x!=9999)//输入9999表示结束
{
s=(LNode*)malloc(sizeof(LNode));//创建新结点
s->data=x;
s->next=L->next;
L->next=s;//将新结点s插入表中,L为头指针
scanf("%d",&x);
}
return L;
}
//尾插法建立单链表
LinkList List_TailInsert(LinkList &L)//得到正向的单链表
{
int x;
L=(LinkList)malloc(sizeof(LNode));
LNode *s;//定义新结点
LNode *r=L;//r为表尾指针,为空链表
scanf("%d",&x);
while(x!=9999)//输入9999表示结束
{
s=(LNode *)malloc(sizeof(LNode));
s->data=x;
r->next=s;
r=s;//r移动到新的表尾节点
scanf("%d",&x);
}
r->next=NULL;//尾指针置空
return L;
}
//按序号查找结点值
LNode *GetElem(LinkList L,int i)
{
int j=1;//计数,初值为1
LNode *p=L->next;//头结点的指针赋给p
if(i==0)//此时查询的是头结点,返回头结点
return L;
if(i<1)//i为无效值,返回NULL
return NULL;
while(p && j<i)//当p非空且未到i的位置
{
p=p->next;//接着往下找
j++;//计数值跟着+1
}//找到第i个结点时退出循环
return p;//返回第i个结点的指针;如果i大于表长则返回尾指针指向的NULL
}
//按值查找表结点
LNode *LocateElem(LinkList L,int e)
{
LNode *p=L->next;//头结点的指针赋给p
while(p!=NULL&&p->data!=e)//当p非空且未找到关键字
p=p->next;//接着往下找
return p;//找到后返回结点指针,否则返回尾指针指向的NULL
}
//插入第i个结点操作
p=GetElem(L,i-1)//找到插入位置i的前驱结点
s->next=p->next;
p->next=s;
//删除第i个结点操作
p=GetElem(L,i-1);//找到删除位置i的前驱结点
q=p->next;//q为被删除结点,即p的后继结点
p->next=q->next;//越俎代庖
free(q);释放结点的存储空间
//将结点s插入到结点p前面
s->next=p->next;//结点s连到结点p后面
p->next=s;
temp=p->data;//交换数据,后插变前插
p->data=s->data;
s->data=temp;
//删除结点p:狸猫换太子
q=p->next;//q指向p的后继结点:狸猫p找到替死鬼q
p->data=p->next->data;//结点p的数据变成后继结点的数据:狸猫变太子
p->next=q->next;//删除q结点:真太子被杀,假太子存活
free(q);//毁尸灭迹
//单链表的类定义,并实现成员函数Reverse
class LinkNode
{
friend class LinkList;
private:
int data;
LinkNode *next;
};
class LinkList
{
public:
LinkList()
{
first=NULL;
}
~LinkList();
private:
LinkNode *first;
};
//将第一个结点之后的每个结点插入到头结点之后,第一个结点之前,实现原地逆序;使用头插法实现
void Reverse(LinkList &L)
{
LinkList *p=L->next;//头结点后的第一个结点,以后新的第一个结点也叫p
LinkList *q;
L->next=NULL;//L此时为空链表,与原链表断开
while(p!=NULL)
{
q=p->next;//暂存待插入结点的下一个结点
p->next=L->next;//结点插入到第一个结点之前
L->next=p;//结点插入到头结点之后
p=q;//继续插入下一个结点
}
}
//类成员函数:单链表实现简单选择排序
//思路:从左往右扫描单链表,选择关键字最小的结点,与当前结点交换关键字
void SelectSort(LinkList &L)
{
LinkNode *p,*q,*min;//p是每趟排序的当前指针,q是遍历指针
p=L->next;
while(p->next!=NULL)//每趟排序
{
q=p->next;//q是p的后继结点
min=p;
while(q!=NULL)//从p结点往后开始查找最小结点
{
if(q->data<min->data)//如果找到更小关键字的结点,则更新min指针
min=q;
q=q->next;//如果没找到,继续向后遍历
}
if(min!=p)//如果找到了最小结点,则进行关键字交换
{
int temp=min->data;
min->data=p->data;
p->data=temp;
}
p=p->next;//继续下一趟排序
}
}
//删除非递减有序的单链表中元素值相同的点:如果某结点等于其下一结点的值,则删除该结点
typedef struct LNode
{
Elemtype data;
struct LNode *next;
}LNode,*LinkList;
void Purge(LinkList &L)
{
LNode *p=L;
while(p->next!=NULL&&p->next->next!=NULL)//下个结点和下下个结点的值相同,则删除下个结点
{
if(p->next->data==p->next->next->data)
{
LNode *q=p->next;
p->next=q->next;
free(q);
}
else
p=p->next;//接着往后遍历
}
}
//补集运算:A=A-B
//思路:依次扫描A中的每个结点,判断是否存在于B中,若存在则删除结点,若不在则接着向后扫描A
void DifferenceSet(LinkList &A,LinkList B)
{
LNode *temp,*p=A,*q;//temp暂存需要删除的结点,pq分别为两个链表的结点指针
while(p->next!=NULL)//链表A没有遍历完
{
q=B;//每次大循环都从头遍历链表B
int flag=0;//是否找到相同结点并删除结点的标志
while(q->next!=NULL&&flag==0)//链表遍历结束或找到相同结点时退出循环
{
if(p->next->data==q->next->data)//如果A的某个结点和B的某个结点值相同
{
temp=p->next;//在A中删除该结点
p->next=temp->next;
free(temp);
flag=1;//表明已找到相同的结点
}
q=q->next;//无论有没有找到,都继续往后扫描链表B
}
if(flag==0)//如果此时A的结点在B中没有相同的结点,则继续扫描链表A
p=p->next;
}
}
//并集运算:AB两个单链表递增有序,要求合并后的链表仍递增有序
void Union(LinkList ha,LinkList hb)
{
LinkList pa=ha->next;
LinkList pb=hb->next;
pc=pa;
while(pa!=NULL&&pb!=NULL)
{
if(pa->data<pb->data)
{
pc->next=pa;
pc=pa;
pa=pa->next;
}
else if(pa->data>pb->data)
{
pc->next=pb;
pc=pb;
pb=pb->next;
}
else
{
pc->next=pa;
pc=pa;
pa=pa->next;
LNode *u=pb;
pb=pb->next;
free(u);
}
}
if(pa!=NULL)
pc->next=pa;
if(pb!=NULL)
pc->next=pb;
free(hb);
}
4.其他链表
4.1双链表
//双链表结点定义
typedef struct DNode
{
int data;
struct DNode *prior,*next;//前驱和后继指针
}DNode,*DLinkList;//分别强调双链表和结点
//初始化双链表
bool InitDLinkList(DLinklist &L)
{
L=(DNode *)malloc(sizeof(DNode));
if(L==NULL)
return false;
L->prior=NULL;
L->next=NULL;
return true;
}
//双链表插入操作
s->next=p->next;
p->next->prior=s;
s->prior=p;
p->next=s;
//双链表删除操作
p->next=q->next;
q->next->prior=p;
free(q);
4.2循环单链表
//初始化一个循环单链表
bool InitList(LinkList &L)
{
L=(LNode *)malloc(sizeof(LNode));
if(L==NULL)
return false;
L->next=L;
return true;
}
4.3循环双链表
//初始化空的循环双链表
bool InitDLinkList(DLinklist &L)
{
L=(DNode *)malloc(sizeof(DNode));
if(L==NULL)
return false;
L->prior=L;
L->next=L;
return true;
}
4.4静态链表
#define MaxSize 50
typedef struct
{
int data;
int next;//下一个元素的数组下标
}SLinkList[MaxSize];//强调本质是一个数组
(三)栈
1.栈的基本操作
InitStack(&S)//初始化一个空栈S
StackEmpty(S)//判断一个栈是否为空,若空则返回true,否则返回false
Push(&S,&x)//进栈,若栈未满,x进栈成为新栈顶
Pop(&S,&x)//出栈,若栈非空,弹出栈顶元素并用x返回
GetTop(S,&x)//读栈顶元素,若栈非空,则用x返回栈顶元素
DestroyStack(&S)//销毁栈,并释放栈S占用的存储空间
2.顺序栈
2.1顺序栈的定义
#define MaxSize 50
typedef struct
{
int data[MaxSize];
int top;//栈顶指针
}SqStack;
2.2顺序栈基本操作的实现
//初始化
void InitStack(SqStack &S)
{
S.top=-1;//初始化为空栈
}
//判栈空
bool StackEmpty(SqStack S)
{
if(S.top==-1)//栈空
return true;
else
return false;
}
//进栈
bool Push(SqStack &S,int x)
{
if(S.top==MaxSize-1)//栈满报错
return false;
S.top++;
S.data[S.top]=x;//指针先+1再入栈
return true;
}
//出栈
bool Pop(SqStack &S,int &x)
{
if(S.top==-1)//栈空报错
return false;
x=S.data[S.top];//先出栈指针再-1
S.top--;
return true;
}
//读栈顶元素
bool GetTop(SqStack S,int &x)
{
if(S.top==-1)
return false;
x=S.data[S.top];
return true;
}
3.共享栈
3.1共享栈的定义
#define MaxSize 10
typedef struct
{
int data[MaxSize];
int top0;
int top1;
}ShStack;
3.2初始化共享栈
void InitStack(ShStack &S)
{
S.top0=-1;
S.top1=MaxSize;
//栈满的条件:top0+1==top1
}
4.链栈
4.1链栈的定义
typedef struct Linknode
{
int data;//数据域
struct Linknode *next;//指针域
}*LiStack;
4.2链栈基本操作的实现
与链表类似
(四)队列
1.队列的基本操作
InitQueue(&Q)//初始化队列;构造一个空队列Q
QueueEmpty(Q)//判队列空;若空返回true,否则返回false
EnQueue(&Q,x)//入队;若未满,将x加入成为新的队尾
DeQueue(&Q,&x)//出队;若非空,删除队头元素,并用x返回
GetHead(Q,&x)//读队头元素;若非空,则将队头元素赋值给x
2.“顺序”队列
#define MaxSize 50//队列中元素的最大个数
typedef struct
{
int data[MaxSize];
int front,rear;//队头指针和队尾指针
}SqQueue;
//初始化队列
void InitQueue(SqQueue &Q)
{
Q.rear=Q.front=0;
}
//入队
bool EnQueue(SqQueue &Q,int x)
{
if()//队列已满
return false;
Q.data[Q.rear]=x;
Q.rear=Q.rear+1;
return 1;
}
3.循环队列
本质还是一种顺序存储结构的队列,在逻辑上视为一个环,称为循环队列
//初始化
void InitQueue(SqQueue &Q)
{
Q.rear=Q.front=0;//初始化队首、队尾指针
}
//判队空
bool isEmpty(SqQueue Q)
{
if(Q.rear==Q.front)//队空条件!!!
return true;
else
return flase;
}
//入队
bool EnQueue(SqQueue &Q,int x)
{
if((Q.rear+1)%MaxSize==Q.front)//队满条件!!!
return flase;
Q.data[Q.rear]=x;
Q.rear=(Q.rear+1)%MaxSize;//队尾指针加1(要取模)
return true;
}
//出队
bool DeQueue(SqQueue &Q,int &x)
{
if(Q.rear==Q.front)//队空条件
return false;
x=Q.data[Q.front];
Q.front=(Q.front+1)%MaxSize;//队头指针加1(要取模)
return true;
}
4.链式队列
4.1链式队列的定义
//链式队列结点
typedef struct
{
int data;
struct LinkNode *next;
}LinkNode;
//链式队列
typedef struct
{
LinkNode *front,*rear;
}LinkQueue;
4.2链式队列基本操作的实现
//初始化
void InitQueue(LinkQueue &Q)
{
Q.front=Q.rear=(LinkNode*)malloc(sizeof(LinkNode));//建立头结点
Q.front->next=NULL;//初始化为空队列
}
//判队空
bool IsEmpty(LinkQueue Q)
{
if(Q.front==Q.rear)//队空条件
return true;
else
return false;
}
//入队
void EnQueue(LinkQueue &Q,int x)
{
LinkNode *s=(LinkNode *)malloc(sizeof(LinkNode));//创建新结点
s->data=x;
s->next=NULL;
Q.rear->next=s;//插入到队尾
Q.rear=s;//更新队尾指针
}
//出队
bool DeQueue(LinkQueue &Q,int &x)
{
if(Q.front==Q.rear)
return false;
LinkNode *p=Q.front->next;//指向要删除的队首元素
x=p->data;
Q.front->next=p->next;//删除队首元素
if(Q.rear==p)//如果原队列中只有一个结点
Q.rear=Q.front;//删除后为空队列
free(p);
return true;
}
5.栈的应用:括号匹配
bool bracketCheck(char str[],int length)
(五)树
1.二叉树
1.1二叉树的顺序存储
#define MaxSize 100
struct TreeNode
{
int value;
bool isEmpty;//结点是否为空
};
1.2二叉树的链式存储
typedef struct BiTNode
{
int data;//数据域
struct BiTNode *lchild,*rchild;//左、右孩子指针
//struct BiTNode *parent;//父结点指针
}BiTNode,*BiTree;//分别强调链和结点
1.3二叉树的遍历
//先序遍历
void PreOrder(BiTree T)
{
if(T!=NULL)//非空
{
visit(T);//访问根节点
PreOrder(T->lchild);//递归遍历左子树
PreOrder(t->rchild);//递归遍历右子树
}
}
//中序遍历
void InOrder(BiTree T)
{
if(T!=NULL)
{
InOrder(T->lchild);//递归遍历左子树
visit(T);//访问根结点
InOrder(T->rchild);//递归遍历右子树
}
}
//后序遍历
void PostOrder(BiTree T)
{
if(T!=NULL)
{
PostOrder(T->lchild);//递归遍历左子树
PostOrder(T->rchild);//递归遍历右子树
visit(T);//访问根结点
}
}
//层次遍历
void LevelOrder(BiTree T)
{
InitQueue(Q);//初始化辅助队列
BiTree p;//辅助结点??
EnQueue(Q,T);//根结点入队
while(!IsEmpty(Q))//辅助队列不空则循环
{
DeQueue(Q,p);//队头结点出队
visit(p);//访问出队结点
if(p->lchild!=NULL)
EnQueue(Q,p->lchild);//左子树不空,则左子树根结点入队
if(p->rchild!=NULL)
EnQueue(Q,p->rchild);//右子树不空,则右子树根结点入队
}
}
//先序遍历的非递归算法
void PreOrder2(BiTree T)
{
InitStack(S);//初始化栈
BiTree p=T;//p为遍历指针
while(p||!IsEmpty(S))//p不空或者栈不空时循环
{
if(p)//一路向左
{
visit(p);//访问当前结点
Push(S,p);//并入栈
p=p->lchild;//若左孩子不空,一直向左走
}
else
{
Pop(S,p);//出栈,并转向出栈结点的右子树
p=p->rchild;//向右子树走,p为出栈结点的右孩子
}//返回循环继续进行判断
}
}
//中序遍历的非递归算法
void InOrder2(BiTree T)
{
InitStack(S);//初始化栈
BiTree p=T;//p为遍历指针
while(p||!IsEmpty(S))//p不空或者栈不空时循环
{
if(p)//一路向左
{
Push(S,p);//入栈
p=p->lchild;//若左孩子不空,一直向左走
}
else
{
Pop(S,p);//出栈,并转向出栈结点的右子树
visit(p);//访问出栈结点
p=p->rchild;//向右子树走,p为出栈结点的右孩子
}//返回循环继续进行判断
}
}
//后序遍历的非递归算法
void PostOrder2(BiTree T)
{
InitStack(S);
BiTree p=T;//p为遍历指针
BiTree r=NULL;//辅助指针,指向最近访问过的结点
while(p||!IsEmpty(S))
{
if(p)//一路向左
{
Push(S,p);//入栈
p=p->lchild;//若左孩子不空,一直向左走
}
else//转向右
{
GetTop(S,p);//读栈顶结点
if(p->rchild&&p->rchild!=r)//若右子树存在且未被访问过
{
p=p->rchild;//转向右子树
Push(S,p);//入栈
p=p->lchild;//一路向左
}
else//右子树不存在或者已被访问过
{
Pop(S,p);//弹出栈顶结点
visit(p->data);//访问该结点
r=p;//记录该结点已被访问
p=NULL;//重置遍历指针
}
}
}
}
//求二叉树中结点的度为1的个数,非递归
//思想:使用先序遍历非递归算法,在原visit函数处添加判断度是否为1的语句即可
typedef struct BiTNode
{
Elemtype data;
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
void Count(BiTree T)
{
BiTree p=T;
InitStack(S);
int n=0;
while(p!=NULL||!IsEmpty(S))//借助先序遍历
{
if(p!=NULL)
{
if((p->lchild!=NULL&&p->rchild==NULL)||(p->lchild==NULL&&p->rchild!=NULL))
n++;
Push(S,p);
p=p->lchild;
}
else
{
Pop(S,p);
p=p->rchild;
}
}
return n;
}
//求二叉树的深度
//递归算法
int GetHeight(BiTree T)
{
int l,r;
if(T==NULL)
return 0;
else
{
l=GetHeight(T->lchild);
r=GetHeight(T->rchild);
return l>r ? l+1 : r+1;
}
}
//非递归算法
int GetHeight(BiTree T)
{
if(T==NULL)
return 0;
InitQueue(Q);
int last=0,level=0;//last指向每层最右结点,level保存层数
EnQueue(Q,T);
BiTree p;
while(!IsEmpty(Q))//借助层次遍历
{
DeQueue(Q,p);
if(p->lchild!=NULL)
EnQueue(Q,p->lchild);
if(p->rchild!=NULL)
EnQueue(Q,p->rchild);
if(Q.front==last)//出队元素刚好是该层最右结点,即该层遍历结束
{
level++;
last=Q.rear;//指向下一层的最右结点
}
}
return level;
}
//计算二叉树的结点总数
int n=0;//全局变量,保存结点总数
void Count(BiTree T)
{
if(T!=NULL)
{
n++;
Count(T->lchild);
Count(T->rchild);
}
}
//判断是否为满二叉树:调用高度函数和结点函数
bool Judge(BiTree T)
{
Count(T);//计算结点总数
int h=GetHeight(T);//计算二叉树的高度
if(pow(2,h)-1==n)//判断是否满足满二叉树的条件
return true;
else
return false;
}
//判断是否为完全二叉树:使用层次遍历,遇到空结点时,查看其后是否有非空结点
bool Judge(BiTree T)
{
if(T==NULL)
return true;
InitQueue(Q);
EnQueue(Q,T);
BiTree p;
while(!IsEmpty(Q))//借助层次遍历
{
DeQueue(Q,p);
if(p!=NULL)//空结点也入队
{
EnQueue(Q,p->lchild);
EnQueue(Q,p->rchild);
}
else//遇到子树为空结点,查看其后结点
{
while(!IsEmpty(Q))
{
DeQueue(Q,p);
if(p!=NULL)//其后结点非空,则不是完全二叉树
return false;
}
}
}
return true;
}
//判断是否为二叉排序树
//思路:二叉排序树的中序遍历序列递增,因此进行中序遍历,后一个值一直比前一个值大,则为二叉排序树
int temp=-∞;
bool Judge(BiTree T)
{
bool l,r;
if(T==NULL)
return true;
else
{
l=Judge(T->lchild);//判断左子树是否为二叉排序树
if(l==false||temp>=T->data)//如果左子树不是二叉排序树或者前一个结点值大于等于当前结点之前,则该二叉树不是二叉排序树
return false;
temp=T->data;//当前树为二叉排序树,保存当前结点的值
r=Judge(T->rchild);//判断右子树是否为二叉排序树
return r;
}
}
//求给定二叉树上从根结点到叶子结点的一条路径长度等于树的深度减一的路径,路径终点”靠左”:先计算树的深度,再使用先序遍历找到符合条件的叶子结点,满足“靠左”条件
typedef struct BiTNode
{
Elemtype data;
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
int GetHeight(BiTree T)
{
int l,r;
if(T==NULL)
return 0;
else
{
l=GetHeight(T->lchild);
r=GetHeight(T->rchild);
return l>r ? l+1:r+1;
}
}
InitStack(S);
int depth=GetHeight(T);
BiTree p=T;
void Path(BiTree p)
{
if(p!=NULL)
{
Push(S,p);
if(p->lchild==NULL&&p->rchild==NULL)//找到叶子结点
{
if(S.top==depth-1)//如果满足条件
{
for(int i=0;i<S.top;i++)//输出路径序列
print(S.data[i]);
return;
}
}
Path(p->lchild);//先序遍历,保证路径终点靠左
Path(p->rchild);
S.top-=1;
}
}
//判断二叉树是否为最大树:父结点大于等于子结点
//使用先序或者中序遍历,将访问语句改为判断语句,如下所示
if(p->lchild!=NULL&&p->lchild->data>p->data)
return false;
if(p->rchild!=NULL&&p->rchild->data>p->data)
return false;
//求二叉树从根结点到指定结点p的路径
//思路:前序遍历二叉树,依次将结点入栈,若找到目标结点,则依次输出栈中结点;否则依次退栈直到找到目标结点
InitStack(S);
void Path(BiTree T,BiTNode *p)
{
if(T!=NULL)
{
Push(S,T);
if(T==p)//若找到目标结点
{
for(int i=0;i<S.top;i++)//依次打印路径结点
print(S.data[i]);
}
return;
Path(T->lchild,p);//若未找到,则转向左右子树
Path(T->rchild,p);
S.top—=1;//依次退栈,继续寻找目标结点
}
}
//求二叉树中相距最远的两个结点间的路径长度
//思路:最长路径可能的值为:左子树最长路径、右子树最长路径、左右子树高度之和;采用贪婪算法,逐步比较三者的值得到局部最优解,使用递归算法实现全局最优解
int GetHeight(BiTree T);//返回树的高度
int Max(int a,int b,int c)//返回三个数的最大值
{
int n;
if(a>b)
n=a;
else
n=b;
if(n<c)
n=c;
return n;
}
int MaxPath(BiTree T)//返回最大路径长度
{
if(T!=NULL)
{
int lpath=MaxPath(T->lchild);//计算左子树最大路径
int rpath=MaxPath(T->rchild);//计算右子树最大路径
int tpath=GetHeight(T->lchild)+GetHeight(T->rchild);//计算左右子树的高度之和
return Max(lpath,rpath,tpath);//返回三个的最大值
}
return 0;//空树返回0
}
//仅包含加减乘除的算术表达式存储在二叉树中,根结点存储运算符,计算该表达式的值
//思路:先分别递归求出左子树和右子树的值,再根据根结点的运算符计算出表达式的值
float Value(BiTree T)
{
float lv,rv,value;//分别为左子树的值、右子树的值、表达式的值
if(T!=NULL)
{
if(T->lchild==NULL&&T->rchild==NULL)//如果为叶结点,直接返回结点值
return T->data;
else
{
lv=Value(T->lchild);//计算左子树的表达式
rv=Value(T->rchild);//计算右子树的表达式
switch(T->data)//判断根结点的运算符
{
case ‘+’:value=lv+rv;break;
case ‘-‘:value=lv-rv;break;
case ‘*’:value=lv*rv;break;
case ‘/’:value=lv/rv;
}
}
}
return value;
}
//计算二叉树的带权路径长度
int wpl=0;
int WPL(BiTree T,int deep)
{
if(T->lchild==NULL&&T->rchild==NULL)//遇到根结点,计算带权路径长度
wpl+=deep*T->data;
if(T->lchild!=NULL)//如果左子树非空,递归计算带权路径长度,且深度加一
WPL(T->lchild,deep+1);
if(T->rchild!=NULL)//如果右子树非空
WPL(T->rchild,deep+1);
return wpl;
}
2.树
2.1树的定义
//双亲表示法:顺序存储
#define MAX_TREE_SIZE 100//最大结点树
typedef struct //树的结点的定义
{
int data;
int parent;//双亲位置
}PTNode;
typedef struct //树的定义
{
PTNode nodes[MAX_TREE_SIZE];
int n;//结点树
}PTree;
//孩子表示法:顺序+链式存储
struct CTNode
{
int child;//孩子结点在数组中的位置
struct CTNode *next;//下一个孩子
};
typedef struct
{
int data;
struct CTNode *firstchild;//第一个孩子
}CTBox;
typedef struct
{
CTBox nodes[MAX_TREE_SIZE];
int n,r;//结点数和根的位置
}CTree;
//孩子兄弟表示法:链式存储
typedef struct CSNode
{
int data;
struct CSNode *firstchild,*nextsibling;//左孩子、右兄弟
}CSNode,*CSTree;
3.二叉排序树
3.1二叉排序树的定义
3.2二叉排序树基本操作的实现
//查找(非递归)
BiTNode *BST_Search(BiTree T,int key)
{
while(T!=NULL&&key!=T->data)//若树空或者关键字等于根结点值
{
if(key<T->data)//若关键字小于根结点值,转向左子树查找
T=T->lchild;
else T=T->rchild;//若关键字大于根结点值,转向右子树查找
}
return T;//若查找成功返回当前结点,查找失败则返回空结点
}
//查找(递归)
BiTNode *BSTSearch(BiTree T,int key)
{
if(T==NULL)
return NULL;
if(key==T->data)
return T;
else if(key<T->data)
return BSTSearch(T->lchild,key);
else
return BSTSearch(T->rchild,key);
}
//插入
void BST_Insert(BiTree &T,int k)
{
if(T==NULL)//若原树为空,则新插入结点作为根结点
{
T=(BiTree)malloc(sizeof(BiTNode));//新建树(根结点)
T->data=k;
T->lchild=T->rchild=NULL;//左右子树初始化为空树
return true;//插入成功,返回1
}
else if(k==T->data)//树中已存在相同关键字的结点,插入失败
return false;
else if(k<T->data)//若关键字小于当前结点的值,插入左子树
return BST_Insert(T->lchild,k);
else//若关键字大于当前结点的值,插入右子树
return BST_Insert(T->rchild,k);
}
//构造
void Creat_BST(BiTree &T,int str[],int n)//str为待插入关键字数组,n为结点树
{
T=NULL;//初始化为空树
int i=0;
while(i<n)//依次将关键字插入到二叉排序树中
{
BST_Insert(T,str[i]);
i++;
}
}
//删除二叉排序中的最小元素:即中序遍历第一个元素;根据是否为根结点、叶结点、带右子树的结点分类讨论
typedef struct BiTNode
{
Elemtype data;
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
void Delete(BiTree T)
{
BiTree p,q;//p是最小结点,q是其父结点
if(T==NULL)//空树
return;
p=T,q=T;
while(p->lchild!=NULL)//一直往左,找到最小结点
{
q=p;
p=p->lchild;
}
if(p==T)//如果最小结点为根结点,即没有左子树,则将右子树作为新的根结点
{
T=p->rchild;
free(p);
}
else//最小结点肯定没有左子树,按是否有右子树来分类讨论
{
if(p->rchild==NULL)//没有右子树,即叶结点,直接删除最小结点
{
q->lchild=NULL;
free(p);
}
else//有右子树,将右子树作为新的最小结点,释放结点
{
q->lchild=p->rchild;
free(p);
}
}
}
(六)图
1.图的基本操作
Adjacent(G,x,y)//判断图是否存在边
Neighbors(G,x)//列出图中与结点x邻接的边
InsertVertex(G,x)//在图中插入顶点x
DeleteVertex(G,x)//在图中删除顶点x
AddEdge(G,x,y)//若该边不存在,则向图中添加该边
RemoveEdge(G,x,y)//若该边存在,则从图中删除该边
FirstNeighbor(G,x)//求图中顶点x的第一个邻接点,有则返回顶点号,没有则返回-1
NextNeighbor(G,x,y)//假设顶点y是顶点x的第一个邻接点,返回除y外顶点x的下一个邻接点;若存在返回顶点号,若y是最后一个邻接点则返回-1
Get_edge_value(G,x,y)//获取边对应的权值
Set_edge_value(G,x,y,v)//设置边对应的权值
2.图的定义
2.1邻接矩阵法
#define MaxVertexNum 100//最大顶点数
typedef char VertexType;//顶点的数据类型
typedef int EdgeType;//带权图中边上权值的数据类型
typedef struct
{
VertexTpye Vex[MaxVertexNum];//顶点表
EdgeType Edge[MaxVertexNum][MaxVertexNum];//邻接矩阵,边表
int vexnum,arcnum;//当前顶点数,弧数
}MGraph;//是以邻接矩阵存储的图类型
//使用邻接矩阵存储非带权无向图,按行映射方式存储下三角矩阵,要求算法实现计算给定顶点的度
int Change(int i,int j)
{
return i*(i-1)/2+j-1;
}
int Count(int m,int B[],int n)
{
int s=0;
for(int i=1;i<=m;i++)
{
int k1=Change(m,i);
s=s+k1;
}
for(int j=m;j<=n;j++)
{
int k2=Change(j,m);
s=s+k2;
}
return s;
}
2.2邻接表法
#define MaxVertexNum 100//最大顶点数
typedef char VertexType;//顶点的数据类型
typedef struct ArcNode//边表结点
{
int adjvex;//该弧所指向的顶点的位置
struct ArcNode *next;//指向下一条弧的指针
InfoType info;//网的边权值
}ArcNode;
typedef struct VNode//顶点表结点
{
VertexType data;//顶点数据
ArcNode *first;//指向第一条依附该顶点的弧的指针
}VNode;
typedef struct//
{
VNode AdjList[MaxVertexNum];//邻接表
int vexnum,arcnum;//图的顶点数和弧数
}ALGraph;//是以邻接表存储的图类型
//计算有向图中出度为0的个数
int Count(ALGraph &G)
{
int n=0;
ArcNode *p;
for(int i=0;i<G.vexnum;i++)//遍历顶点表
{
p=G.AdjList[i].first;//左边是实体对象则用.;左边是指针则用->
if(p==NULL)
n++;
}
return n;
}
//删除有向图的一个弧(i,j),以邻接表存储
//思路:在顶点表中找到顶点i,根据表头指针找到顶点j所在的边表;扫描边表,若第一个结点为目标结点,则直接删除;否则继续往后扫描,删除后保证不断链
void Delete(ALGraph &G,int i,int j)
{
VNode p;//顶点表的某个结点,指向i
ArcNode *q,*r;//边表结点指针,r是q的前继
p=G.AdjList[i];
q=p->first;//指针q初始化为头指针
if(q->adjvex==j)//如果顶点j刚好是头指针,则不需要遍历,删除也比较方便
p->first=q->next;//跳过q所指向的头指针即完成删除
else//否则需要往后遍历寻找
{
r=p->first//r在前,q在后,一起向后遍历,保证不断链
q=r->next;
while(q->adjvex!=j)
{
q=q->next;
r=r->next;
}
r->next=q->next;//此时找到目标顶点,删除q指向的结点,跳过该结点即可
}
free(q);//统一释放q指针,即目标结点
}
//有向图以邻接表存储,编写算法增加弧(i,j)
//思路:扫描边表,如果找不到结点j则在边表最后插入新的j结点;如果已存在则返回
void ADD(ALGraph &G)
{
VNode *p;
ArcNode *q,*r;
p=G.AdjList[i];//顶点i
r=p;
q=p->first;
while(q!=NULL&&q->adjvex!=j)//遍历边表
{
r=q;
q=q->next;
}
if(q==NULL)//如果边表为空或未找到目标结点,则新建结点并插入
{
ArcNode *JNode=(ArcNode *)malloc(sizeof(ArcNode));
JNode->adjvex=j;
r->next=JNode;
JNode->next=NULL;
}
else//如果已存在,则返回
return;
}
//邻接矩阵转为邻接表
void Convert(MGraph MG,ALGraph &AG)
{
int i,j,n=MG.vexnum;
ArcNode *p;
AG=(ALGraph *)malloc(sizeof(ALGraph));
for(i=0;i<n;i++)
AG->AdjList[i].first=NULL;//顶点表结点的指针赋初值为空
for(i=0;i<n;i++)//遍历邻接矩阵,按行
{
for(j=n-1;j>=0;j—-)//按列
{
if(MG.Edge[i][j]!=0)//对边表进行操作
{
p=(ArcNode *)malloc(sizeof(ArcNode));
p->adjvex=j;//顶点i指向的顶点j
p->info=MG.Edge[i][j];//权值
p->next=AG->AdjList[i].first;//前插法,先将新结点插入在first前面,再前移更新first实现后插操作
AG->AdjList[i].first=p;
}
}
}
}
//邻接表转为邻接矩阵
void Convert(ALGraph AG,MGraph &MG)
{
ArcNode *p;
for(int i=0;i<n;i++)//遍历顶点表
{
p=AG->AdjList[i].first;//遍历指针
while(p!=NULL)//遍历边表
{
MG.Edge[i][p->adjvex]=1;//对应矩阵元素置1
p=p->next;
}
}
}
3.图的遍历
3.1广度优先搜索BFS
bool visited[MAX_VERTEX_NUM];//访问标记数组
void BFSTraverse(Graph G)//对图G进行广度优先遍历,防止有非连通的图
{
for(i=0;i<G.vernum;++i)
visited[i]=false;//访问标记数组初始化
InitQueue(Q);//初始化辅助队列
for(i=0;i<G.vexnum;++i)//从0号顶点开始遍历
{
if(visited[i]==false)//对每个连通分量调用一次BFS
BFS(G,i);//i没访问过,从i开始BFS
}
}
void BFS(Graph G,int v)//从顶点v出发,广度优先遍历图G
{
visit(v);//访问初始顶点v
visited[v]=true;//对v做已访问标记
Enqueue(Q,v);//顶点v入队
while(!IsEmpty(Q))
{
DeQueue(Q,v);//顶点v出队
for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w))//检测v所有邻接点
{
if(visited[w]==false)//w为v的尚未访问的邻接顶点
{
visit(w);//访问顶点w
visited[w]=true;//对w做已访问标记
EnQueue(Q,w);//顶点w入队
}
}
}
}
//应用:求解带权无向图中距离顶点v距离最远的一个顶点
//思路:使用广度优先遍历,设置一个变量记录访问的结点,最后访问的结点即最远的顶点
int x;
BFS(G,i,x);
//visit语句改成x赋值语句
x=v;
x=w;
return x;
3.2 深度优先搜索DFS
bool visited[MAX_VERTEX_NUM];//访问标记数组
void DFSTraverse(Graph G)//对图G进行深度优先遍历
{
for(v=0;v<G.vexnum;++v)
visited[v]=false;//初始化已访问标记数据
for(v=0;v<G.vexnum;++v)//从v=0开始遍历
{
if(visited[v]==false)
DFS(G,v);
}
}
void DFS(Graph G,int v)//从顶点v出发,深度优先遍历图G
{
visit(v);
visited[v]=true;
for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w))
{
if(visited[w]==false)//w为u的尚未访问的邻接顶点
DFS(G,w);
}
}
//判断无向图中任意给定的两个顶点之间是否存在一条长度为k的路径
//思路:使用深度优先遍历,当访问到目标结点时,判断路径长度是否为k
int k;
bool FindPath(Graph G,int u,int v,int d)
{
ArcNode *p;
int w;//遍历辅助结点
d++;//当前路径长度
visited[u]=true;//访问标记数组
if(u==v&&d==k)
return true;
p=G.AdjList[u].first;//在边表里面查找目标结点
while(p!=NULL)
{
w=p->adjvex;//w是边表结点的顶点号
if(visited[w]==false&&FindPath(G,w,v,d)==true)//如果能找到目标结点
return true;
p=p->next;//找不到就继续遍历
}
visited[u]=false;//回退上一个结点,访问其下一个邻接点
return false;
}
4.最小生成树
4.1通用算法
GENERIC_MST(G)
{
T=NULL;
while T未形成一棵生成树;
{
do 找到一条最小代价边(u,v)并且加入T后不会产生回路;
{
T=T∪(u,v);
}
}
}
4.2Prim算法
void Prim(G,T)
{
T=空集;//初始化空树
U={w};//添加任一顶点w
while((V-U)!=空集)//若树中不含全部顶点
{
设(u,v)是使u∈U与v∈(V-U),且权值最小的边;
T=T∪{(u,v)};//边归入树
U=U∪{v};//顶点归入树
}
}
4.3Kruskal
void Kruskal(V,T)
{
T=V;//初始化树T,仅含顶点
numS=n;//连通分量数
while(numS>1)//若连通分量数大于1
{
从E中取出权值最小的边(v,u);
if(v和u属于T中不同的连通分量)
{
T=T∪{(v,u)};//将此边加入生成树中
numS--;//连通分量数减1
}
}
}
5.最短路径
5.1Dijkstra算法
5.2Floyd算法
5.3BFS算法
void BFS_MIN_Distance(Graph G,int u)
{
for(i=0;i<G.vexnum;++i)
d[i]=∞;//初始化路径长度
visited[u]=true;
d[u]=0;
EnQueue(Q,u);
while(!IsEmpty(Q))//BFS算法主过程
{
DeQueue(Q,u);//队头元素u出队
for(w=FirstNeighbor(G,u);w>=0;w=NextNeighbor(G,u,w))
{
if(!visited[w])//w为u的尚未访问的邻接顶点
{
visited[w]=true;//设已访问标记
d[w]=d[u]+1;//路径长度+1
EnQueue(Q,w);//顶点w入队
}
}
}
}
6.拓扑排序
bool Topological(Graph G)
{
InitStack(S);//初始化栈,存储入度为0的顶点
for(int i=0;i<G.vexnum;i++)
{
if(indegree[i]==0)
Push(S,i);//将所有入度为0的顶点进栈
}
int count=0;//计数,记录当前已经输出的顶点数
while(!IsEmpty(S))//栈不空,则存在入度为0的顶点
{
Pop(S,i);//栈顶元素出栈
print[count++]=i;//输出顶点i
for(p=G.vertices[i].firstarc;p;p=p->nextarc)//将所有i指向的顶点的入度减1,并将入度减为0的顶点压入栈S
{
v=p->adjvex;
if(!(--indegree[v]))
Push(S,v);//入度为0则入栈
}
}
if(count<G.vexnum)
return false;//排序失败,有向图中有回路
else
return true;//拓扑排序成功
}
//逆拓扑排序(DFS算法)
void DFSTraverse(Graph G)//可能存在不连通分量
{
for(v=0;v<G.vexnum;++v)
visited[v]=false;//初始化已访问标记数组
for(v=0;v<G.vexnum;++v)
if(!visited[v])
DFS(G,v);
}
void DFS(Graph G,int v)//从顶点v出发,深度优先遍历图G
{
visited[v]=true;
for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w))
{
if(!visited[w])//w为v尚未访问的邻接顶点
DFS(G,w)
}
print(v);//输出顶点
}
(七)散列表
(八)并查集
1.并查集的定义
#define SIZE 100
int UFSets[SIZE];//集合元素数组
2.并查集基本操作的实现
//初始化
void Initial(int S[])//S为并查集
{
for(int i=0;i<size;i++)//每个自成单元素集合
S[i]=-1;
}
//Find操作:函数在并查集中查找并返回包含元素x的树的根
int Find(int S[],int x)
{
while(S[x]>=0)//循环寻址x的根
x=S[x];
return x;
}
//Union操作:函数求两个不相交子集合的并集
void Union(int S[],int Root1,int Root2)//表示两个不同的子集合
{
S[Root2]=Root1;//将根Root2连接到根Root1下面
}
三、查找
(一)顺序查找
typedef struct//查找表的数据结构
{
int *elem;//元素存储空间基址,建表时按实际长度分配,0号单元留空
int TableLen;//表的长度
}SSTable;
//带哨兵算法
int Search_Seq(SSTable ST,int key)
{
ST.elem[0]=key;//哨兵
for(i=ST.TableLen;ST.elem[i]!=key;--i)//从后往前找
return i;//若表中不存在关键字为key的元素,将查找到i为0时退出for循环
}
(二)折半查找
int Binary_Search(SeqList L,int key)
{
int low=0,high=L.TableLen-1,mid;
while(low<=high)
{
mid(low+high)/2;//取中间位置
if(L.elem[mid]==key)
return mid;//查找成功则返回所在位置
else if(L.elem[mid]>key)
high=mid-1;//从前半部分继续查找
else
low=mid+1;//从后半部分继续查找
}
return -1;//查找失败,返回-1
}
(三)分块查找
(四)B树
四、排序
(一)直接插入排序
//带哨兵算法
//思路:从后面选择元素直接插入前面已排部分序列
void InsertSort(int A[],int n)
{
int i,j;
for(i=2;i<=n;i++)//将A[2]~A[n]插入到前面已排序序列
{
if(A[i]<A[i-1])//若A[i]关键码小于其前驱,将A[i]插入有序表
{
A[0]=A[i];//复制为哨兵,A[0]不存放元素
for(j=i-1;A[0]<A[j];--j)//从后往前查找待插入位置
A[j+1]=A[j];//向后挪
A[j+1]=A[0];//复制到插入位置
}
}
}
(二)折半插入排序
void InsertSort(int A[],int n)
{
int i,j,low,high,mid;
for(i=2;i<=n;i++)//将A[2]~A[n]插入到前面已排序序列
{
A[0]=A[i];//将A[i]暂存到A[0]
low=1;//设置折半查找的范围
high=i-1;
while(low<=high)//折半查找(默认递增)
{
mid=(low+high)/2;//取中间点
if(A[0]<A[mid]) high=mid-1;//查找左子表
else low=mid+1;//查找右子表
}
for(j=i-1;j>=high+1;--j)
A[j+1]=A[j];//统一后移元素,空出插入位置
A[high+1]=A[0];//插入操作
}
}
(三)冒泡排序
//不发生交换即排序完成
void BubbleSort(int A[],int n)
{
for(i=0;i<n-1;i++)
{
flag=false;//表示本趟冒泡是否发生交换的标志
for(j=n-1;j>i;j--)//一趟冒泡过程
{
if(A[j-1]>A[j])//如果逆序
{
swap(A[j-1],A[j]);//交换
flag=true;
}
}
if(flag==false)
return;//本趟遍历后没有发生交换,说明表已经有序
}
}
(四)快速排序
//选择枢轴元素,分而治之,递归算法
void QuickSort(int A[],int low,int high)
{
if(low<high)//递归跳出的条件
{
int mid=Partition(A,low,high);//划分
QuickSort(A,low,mid-1);//依次对两个子表进行递归排序
QuickSort(A,mid+1,high);
}
}
int Partition(int A[],int low,int high)//一趟划分
{
int temp=A[low];//将当前表中第一个元素设为枢轴,对表进行划分
while(low<high)//循环跳出条件
{
while(low<high&&A[high]>=temp)
--high;
A[low]=A[high];//将比枢轴小的元素移动到左端
while(low<high&&A[low]<=temp)
++low;
A[high]=A[low];//将比枢轴大的元素移动到右端
}
A[low]=pivot;//枢轴元素存放到最终位置
return low;//返回存放枢轴的最终位置
}
(五)简单选择排序
//每趟从当前位置往后选择最小的元素与当前位置元素交换,一共进行n-1轮
void SelectSort(int A[],int n)
{
for(int i=0;i<n-1;i++)//一共进行n-1趟
{
int min=1;//记录最小元素位置
for(int j=i+1;j<n;j++)//在A[i...n-1]中选择最小的元素
{
if(A[j]<A[min])
min=j;//更新最小元素位置
}
if(min!=i)//自身就是最小元素,则不发生交换
swap(A[i],A[min]);//swap函数共移动元素3次
}
}
(六)堆排序
//建立大根堆
void BuildMaxHeap(int A[],int len)
{
for(int i=len/2;i>0;i--)//从i=[n/2]~1,反复调整堆
HeadAdjust(A,i,len);
}
void HeadAdjust(int A[],int k,int len)//将元素k为根的子树进行调整
{
A[0]=A[k];//A[0]暂存子树的根结点
for(i=2*k;i<=len;i=i*2;)//沿key较大的子结点向下筛选
{
if(i<len&&A[i]<A[i+1])
i++;//取key较大的子结点的下标
if(A[0]>=A[i])
break;//筛选结束
else
{
A[k]=A[i];//将A[i]调整到双亲结点上
k=i;//修改k值,以便继续向下筛选
}
}//for结束
A[k]=A[0];//被筛选结点的值放入最终位置
}
//堆排序
void HeapSort(int A[],int len)
{
BuildMaxHeap(A,len);//初始建堆
for(i=len;i>1;i--)//n-1趟的交换和调整堆过程
{
Swap(A[i],A[1]);//堆顶元素和堆底元素交换,后面调整堆时忽略堆底元素
HeadAdjust(A,1,i-1);//调整,把剩余的i-1个元素整理成堆
}
}
判断大根堆:最大树+完全二叉树
判断小根堆:最小树+完全二叉树
(七)归并排序
int *B=(int *)malloc(n*sizeof(int));//辅助数组
void Merge(int A[],int low,int mid,int high)//把A各自有序的两段合并成一个有序表
{
int i,j,k;
for(k=low;k<=high;k++)
B[k]=A[k];//将A中所有元素复制到B中
for(i=low,j=mid+1,k=i;i<=mid&&j<=high;k++)
{
if(B[i]<=B[j])//比较B的左右两段中的元素,小于等于保证了稳定性
A[k]=B[i++];//将较小值复制到A中
else A[k]=B[j++];
}
while(i<=mid)
A[k++]=B[i++];//若第一个表未检测完,复制
while(j<=high)
A[k++]=B[j++];//若第二个表未检测完,复制
}
void MergeSort(int A[],int low,int high)
{
if(low<high)
{
int mid=(low+high)/2;//从中间划分两个子序列
MergeSort(A,low,mid);//对左侧子序列进行递归排序
MergeSort(A,mid+1,high);//对右侧子序列进行递归排序
Merge(A,low,mid,high);//归并
}
}