提示:来源于《王道考研》,《数据结构》相关书籍;有一些代码是我自己写的,可能会有错误,望指出。
一、线性表
1、 顺序表
1.1.1、顺序表的定义
静态
#define MaxSize 100
typedef struct {
ElemType data[MaxSize] //用静态的“数组”存放数据元素
int length;
}SqList //顺序表类型的定义(静态分配方式)
动态
#define InitSize 10
typedef struct {
ElemType *data; //指示动态分配数组的指针
int MaxSize;
int length;
}SeqList //顺序表类型的定义(静态分配方式)
1.1.2、顺序表的初始化
静态
void InitList(SqList &L){
for(int i=0;i<Maxsize;i++)
L.data[i]=0; //将所有元素设置为默认初始值
//如果这里不初始化设置默认值,将会出现非法打印出内存残留的脏数据。
L.length=0;
}
int main(){
SqList L; //声明一个顺序表
InitList(L); //初始化顺序表
return 0;
}
动态初始化
void InitList(SqList &L){
//用malloc函数申请一片连续的存储空间
L.data=(ElemType *)malloc(InitSize*sizeof(ElemType))
L.length=0;
L.MaxSize=InitSize
}
int main(){
SqList L; //声明一个顺序表
InitList(L); //初始化顺序表
IncreaseSize(L,len);//往顺序表中随便插入几个元素
return 0;
}
void IncreaseSize(SeqList &l,int len){
ElemType *p=L.data;
L.data=(int *)malloc((L.MaxSize+len)*sizeof(ElemType));//开辟了一整个连续空间
for(int i=0;i<L.length;i++){
L.data[i]=p[i]; //将数据复制到新区域
}
L.MaxSize=L.MaxSize+len; //顺序表最大长度增加len
free(p);
}
1.1.3、顺序表的基本操作
增加
bool ListInsert(SqList &L,int i,ElemType e){
if(i>L.length+1||i<1) //判断i的范围是否有效 (length+1>i>1)
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(&L,i,&e){ //其中&e 是将删除元素数据输出。
if(i>L.length||i<0)
return false;
e=L.data[i-1];
for(int j=i;j<L.length;j++)
L.data[j-1]=L.data[j]
L.length--;
return ture;
}
总结:其中L.length,MaxSize,i,都是从1开始的
而数组下标是从零开始的。
查找
按位查找
动态初始化
ElemType GetElem(SeqList L,int i){
return L.data[i-1]; //可以随机查找数据
}
按值查找
int locateElem(SeqList L,ElemType e){
for(int i=0;i<L.length;i++)
if(L.data[i]==e) //如果这里是定义的结构类型那么就需要按照结构类型里的每个数据逐个比较!
return i+1;
return 0;
}
1.2、单链表
1.2.1、单链表的定义
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode,*LinkList;
带头结点
bool InitList(LinkList &L){
L=(LNode *)malloc(sizeof(LNode));
if(L==NULL)//申请失败,内存不足
return false;
L->next=NULL;//头结点之后还没有任何结点
return true;
}
不带头结点
bool InitList(LinkList &L){
L=NULL; //防止脏数据
return true;
}
1.2.2、单链表的基本操作
不管是插入操作还是删除操作,单链表我们都无法从后往前逆序查找,所以我们只能从先往后依序查找,故,我们循环指针只能停留在-当前要插入结点的前一个结点- 循环至i-1是因为在i个结点之前插入结点
后插入
带头
bool ListInsert(&L,i,e)
{
//判断合法性
if(i<0)
return false;
LNode *p=L;
int j=0;//当前p指针指向的是第几个结点
while(p!=NULL && j<i-1) {//将p指针循环到i-1的位置
p=p->next;
j++
}
if(p==NULL) //i不合法
return false;
LNode *q=(LNode *)malloc(sizeof(LNode));
if(q==NULL) //内存分配失败
return false;
q->data=e;
q->next=p->next;
p->next=q;
return true;
}
不带头
bool ListInsert(LikeList &L,int i, ElemType e){
if(i<0)
return false;
if(L==NULL)//无头链表插入第一个元素和其他元素操作不同
{
LNode *q=(LNode *)malloc(sizeof(LNode));
if(q==NULL) //内存分配失败
return false;
q->data=e;
q->next=NULL;
L=q;
return true;
}
LNode *p=L;//p指向的是第一个节点元素,而非头结点
int j=1;//零号在前面已经被特殊处理过
while(p!=NULL && j<i-1){
p=p->next;
j++;
}
if(p==NULL) //i值不合法
return false;
LNode *q=(LNode *)malloc(sizeof(LNode));
if(q==NULL) //内存分配失败
return false;
q->data=e;
q->next=p->next;
p->next=q;
return true;
}
面向对象
//可以将上式共同部分拆下来组建新的方法函数。
bool InsertNextLNode( LNode *p,ElemType e){
if(p==NULL) //p不合法
return false;
LNode *q=(LNode *)malloc(sizeof(LNode));
if(q==NULL) //内存分配失败
return false;
q->data=e;
q->next=p->next;
p->next=q;
return true;
}
前插入
== 虽然我们不能往前找结点,但是我们以交换各个结点的值,用p指向的数据跑路。 ==
bool InsertPriorLNode(LNode *p,ElemType e){
if(p==NULL) //p结点不合法
return false;
LNode *s=(LNode *)malloc(sizeof(LNode));
if(s==NULL) //没有足足够的空间
return false;
s->next=p->next; //其实就是后插+换值
p->next=s;
s->data=p->data;
p->data=e;
return true;
}
删除
带头结点(按位序删除)
bool ListDelete(LinkList &L,int i,ElemType &e){
if(i<1) //i值不合法
return false;
LNode *p=L;
int j=0;
while (L!=NULL && j<i-1)
p=p->next;
j++;
if(p==NULL) //i值不合法
return false;
if(p->next==NULL) //i-1个结点之后已无其他节点
return false;
LNode *q=p->next;
e=q->data;
p->next = q->next;
free(q);
return true;
}
删除指定结点
bool LNodeDelete(LNode *p){ //删除指定p结点,传过来的p结点是要删除结点的前一个结点。
if(p==NULL||p->next == NULL)
return false;
LNode *q=p->next;
p->next=q->next;
free(q);
return true;
}//如果指定结点是最后一个结点时,需要特殊处理!
不带头结点(按位序删除)
bool ListDelete(LinkList &L,int i,ElemType &e){
if(i<1) //i值不合法
return false;
if(L->next==NULL){//头结点特殊处理。
LNode *s=L;
L->NULL;
free(s);
return true;
}
LNode *p=L;
int j=1; //这里是最重要的
while (L!=NULL && j<i-1)
p=p->next;
j++;
if(p==NULL) //i值不合法
return false;
if(p->next==NULL) //i-1个结点之后已无其他节点
return false;
LNode *q=p->next;
e=q->data;
p->next = q->next;
free(q);
return true;
}
查找
按位查找
LNode * GetElem(LinkList L,int i){
if(i<0)
return NULL;
LNode *p=L;
int j=0;
while(p!=NULL && j<i){//注意跳出循环条件
p=p->next;
j++;
}
return p;
}
按值查找
LNode * LocateElem(LinkList L,ElemType e){
LNode *p=L->next;//带头
//不带头 LNode *p=L;
while(p!=NULL && p->data!=e)
{
p=p->next;
}
return p;
}
求链表长度
//带头结点
int GetLength(LinkList L){
int len=0;
LNode *p=L;
while(p->next!=NULL){
p=p->next;
len++;
}
return len;
}
//不带头结点
int GetLength(LinkList L){
int len=0;
LNode *p=L;
while(p!=NULL){
p=p->next;
len++;
}
return len;
}
建立
后插入
//带头结点
LinkList List_TailInsert(LinkList &L){
int x;
L=(LinkList)malloc(sizeof(LNode));//建立头结点。
LNode *s,*r=L;
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;
}
//不带头结点
LinkList List_TailInsert(LinkList &L){
int i;
LNode *s;
scanf("%d",&x);
if(L==NULL&&i!=99999){ //不带头结点的第一个结点需要特殊处理
s=(LNode *)malloc(sizeof(LNode));
s->data=x;
s->next=NULL;
L=s;
}
LNode *r=L;
scanf("%d",&x);
while(i!=99999){
s=(LNode *)malloc(sizeof(LNode));
s->data=x;
r->next=s;
r=s;
scanf("%d",&x);
}
r->next=NULL;
return L;
}
头插法
//对带头链表的头结点进行操作
LinkList List_HeadInsert(LinkList &L){
int x;
LNode *s;
L=(LinkList)malloc(sizeof(LNode));
if(L==NULL)
return L;
L->next=NULL;
scanf("%d",&x);
while(x!=9999){
s=(LNode *)malloc(sizeof(LNode));
if(s==NULL)
break;
s->data=x;//与后插入结点的操作是相对应的。
s->next=L->next;
L->next=s;//将新节点插入表中,L为头指针
scanf("%d",&x);
}
return L;
}
后插入
不带头结点
//对不带头链表的头结点进行操作
LinkList List_HeadInsert(LinkList &L){
int x;
LNode *s;
L=NULL;
scanf("%d",&x);
while(x!=99999){
s=(LNode *)malloc(sizeof(LNode));
s->data=x;
s->next=L;
L=s;
scanf("%d",&x);
}
return L;
}
链表的逆置
//带头结点逆置
LinkList ReverseList(LinkList &L) {
LNode *p, *s;
//p = L->next;
s = L->next;
L->next = NULL;
while (s != NULL) {
p = s;
s = s->next;//先移动指针位置
p->next = L->next;//再断开了原始的链接
L->next = p;
}
return L;
}
不带头结点单链表逆置
//不带头结点链表逆置
LinkList ReverseList(LinkList &L) {
LNode *p, *s;
//p = L->next;
s = L;
L= NULL;
while (s != NULL) {
p = s;
s = s->next;//先移动指针位置
p->next = L;//再断开了原始的链接
L = p;
}
return L;
}
注:单链表逆置并非要创建链表与结点,直接移动原链表的结点头插法插入新链表即可!
1.3、双链表
1.3.1、定义
typedef struct DNode{
ElemType data;
struct DNode *next,*prior;
}DNode,*DLinkList;
初始化
//带头结点初始化
bool InitDLinkList(DLinkList &L){
L=(DNode *)malloc(sizeof(DNode));
if(L=NULL)
return false;
L->next=NULL; //头结点之后暂时还没有结点。
L->prior=NULL; //头结点之前prior永远指向NULL
return true;
}
//不带头结点初始化
bool InitDLinkList(DLinkList &L){
L=NULL;
return true;
}
1.3.2、基本操作
插入
带头结点
//在p结点之后插入s结点
bool InsertDLinkList(DNode *p,DNode *s){
if(p==NULL||s==NULL) //非法参数
return false;
s->next=p->next;
if(p->next != NULL)//如果p有后续结点
p->next->prior=s;
s->prior=p;
p->next=s;
return true;
}
//在p结点之后按位前插操作
bool InsertBitPriorDLinkList(DLinkList &L,int i,ElemType e){
if(L==NULL||L->next==NULL) //L链表为空 L链表没有结点
return false;
if(i<0) //i值不合法
return false;
DNode *p=L->next;//头结点之前是不可以插入结点的
for(int j=1;j<i;j++){
p=p->next;
}
if(p==NULL); //i值不合法
return false;
DNode *s=(DNode *)malloc(sizeof(DNode));
if(s==NULL) //没有足够的内存空间
return false;
s->prior=p->prior;
s->next=p;
p->prior->next=s;
p->prior=s;
}
不带头结点
//不带头链表在p结点之后按位前插操作
bool InsertDLinkList(DLinkList &L,int i, ElemType e){
if(i<0) //i值不合法
return false;
DNode *s,*p=L
int j=0;
if(L==NULL)//第一个元素需要特殊处理
{
s=(DNode *)malloc(sizeof(DNode));
s->data=e;
s->next=NULL;
s->prior=NULL;
L=s;
return true;
}
while(j<i)
p=p->next;
j++
if(p==NULL)
return false;
s=(DNode *)malloc(sizeof(DNode));
s->data=e;
s->next=p
s->prior=p->prior;
if(p->prior!=NULL)
p->prior->next=s
p->prior=s;
return true;
}
删除
带头结点的删除。
//删除p结点的后续结点
void DeleteNextDNode(DNode *p){
if(p==NULL) //p结点不存在
return false;
DNode *q=p->next;//找到要删除的结点
if(q==NULL) //没有后继结点
return false;
p->next=q->next;
if(q->next!= NULL)//删除的结点不是最后一个结点
q->next->prior=p;
free(q);
return true;
}
//释放双链表
void DestroyDLinkList(DLinkList &L){
wile(L!=NULL){
DeleteNextDNode(L)
}
free(L); //释放头结点
L->NULL; //指针要轮空
}
如果是不带头结点的链表删除;删除第一个结点时需要特殊操作!
注意:
1,在申请一个动态结点时就要判断它是否为空。
2,在每次遍历后要判断p借点是否为空,如果为空则 i 的值越界
3,在双链表中需要判断该结点是不是最后一个结点,最后一个结点需要特殊处理。
遍历
//向前遍历
while(p!=NULL)
p=>p-prior;
//向后遍历
while(p!=NULL)
p=>p-next;
//向前遍历跳过头结点
while(p->prior!=NULL)
p=>p-prior;
关于for指针移动小结:
1、 for(j=0;j<i;j++) //将指针j移动到当前第i个结点处
2、 for(j=0;j<i-1;j++) //将指针 j 移动到当前第 i 个结点的前一个结点处
以此类推、注意链表是前操作还是后操作,注意他们的for循环条件!
1.4、循环链表
1.4.1、单循环链表
1.4.1.1、定义
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode, *LinkList;
**初始化**
带头结点
bool InitLinkList(LinkList &L){
//带头结点
L=(LNode *)malloc(sizeof(LNode));
if(L==NULL) //没有足够的内存空间
return false;
L->next=L;
return true;
}
不带头结点
bool InitLinkList(LinkList &L){
//不带头结点
L=NULL;
return true;
}
判断空、判断尾结点
//判空
bool Empty(LinkList L){
if(L->next==L)
return true;
else
return false;
}
//判满
bool isTail(LinkList L,LNode *p){
if(p->next==L)
return true;
else
return false;
}
1.4.1.2、基本操作
循环单链表
带头结点-与单链表的添加一样
//添加
bool LinkListAdd(LinkList &L,int i,ElemType e){
if(i<0) return false;
LNode *p=L;
for(int j=0;j<i-1;j++){
p=p->next;
}
if(p==NULL) return false;
LNode *q=(LNode *)malloc(sizeof(LNode));
if(q==NULL) return false;
q->data=e;
q->next=p->next;
p->next=q;
return true;
}
1.4.2、双循环链表
1.4.2.1、 定义
typedef struct DNode{
ElemType data;
struct DNode *prior,*next;
}DNode, *DLinklist;
初始化
bool InitDListList(DLinklist &L){
L=(DNode *)malloc(sizeof(DNode));
if(L==NULL) //内存不足,分配失败。
return false;
L->prior=L; //头结点的prior指向头结点
L->next=L; //头结点的next指向头结点
return true;
}
判空
//双循环链表是否为空
bool Empty(DLinklist L){
if(L->next==L)
return true;
else
return false;
}
判满
bool isTail(DLinklist L,DNode *P){
if(p->next==L)
return true;
else
return false;
}
1.4.2.2、基本操作
插入
bool InsertNextDNode(DNode &p,DNode *s){
if(s==NULL || p==NULL) //两个节点不能为空!
return false;
s->next=p->next;
s->prior=p;
p->next->prior=s;//循环链表不用判断是不是最后一个结点!
p->next=s;
}
删除
bool DeleteNextDNode(DNode *p){
if(p==NULL||p->next=NULL)
return false;
DNode *q=p->next;
p->next=q->next;
q->next->prior=p;
free(q);
}
二、栈和队列
2.1、栈
2.1.1、顺序栈
2.1.1.1、定义
#define MaxSize 10
typedef struct {
ElemType data[MaxSize];//静态数组存放栈中元素
int top;
}SqStack;
初始化
void InitSqstack(SqStack &S){
s.top=-1;//这里的初始化栈顶指针不一样 则算法就相对不一样!
}
判空
bool Empty(SqStack s){
if(s.top==-1)
return true;
else
return false;
}
2.1.1.2、基本操作
入栈操作
//初始化top=-1时
bool Push(SqStack &s,ElemType e){
if(s.top==MaxSize-1)//栈满,报错 //top 起址0;MaxSize 起址1
return false;
s.top=s.top+1;
s.data[s.top]=e;//将两句合并成一句:s.data[++s.top]=e
return true;
}
//初始化top=0时
bool Push(SqStack &s,ElemType e){
if(s.top==MaxSize)//栈满,报错,与top=-1时不同
return false;
s.data[s.top]=e;//始终要记住顺序栈是数组!!
s.top=s.top+1;//将两句合并成一句:s.data[s.top++]=e
return true;
}
出栈操作
top=-1
bool Pop(SqStack &s,ElemType &e){
if(s.top=-1)//空栈
return false;
e=s.data[s.top--]; //与入栈的两句代码一致!
return true;
}
top=1
bool Pop(SqStack &s,ElemType &e){
if(s.top=1)//空栈
return false;
e=s.data[--s.top];
return true;
}
读取栈顶元素
bool GetTop(SqStack s,ElemType &e){
if(s.top==-1)
return false;
e=s.data[s.top];
return true;
}
2.1.1.3、共享栈
定义
#define MaxSize 10
typedef struct{
ElemType data[MaxSize];
int top0;
int top1;
}ShStack;
初始化
void InitShStack(ShStack &s){
s.top0=-1;
s.top1=MaxSize;
}
判满条件:top0+1=top1
//添加元素
void EnStack_top0(ShStack &s,ElemType e){
if(top0+1==top1) return false;
s.data[++top0]=e;
return true;
}
void EnStack_top1(ShStack &s,ElemType e){
if(top1-1==top0) return false;
s.data[--top1]=e;
return true;
}
注:数组分配声明的栈,函数运行完成后系统会自动收回内存。
2.1.2、链栈
2.1.2.1、定义
1.2、单链表
定义
typedef struct LNode{
ElemType data;
struct LNode *next;//栈类型定义
}*LinkList;
初始化
//带头结点初始化
bool InitLinkList(LinkList &L){
L=(LNode *)malloc(sizeof(LNode));
if(L==NULL)
return false;
L->next=NULL;
return true;
}
//不带头结点初始化
void InitLinkList(LinkList &L){
L=NULL;
}
2.1.2.2、基本操作
带头结点操作
进栈-只能在队头操作-此处的p结点就是 头结点 !
//在p结点后插入一个元素
bool Push(LNode *p,ElemType e){//对应单链表的InsertNextNode
if(p==NULL)
return false;
LNode *s=(LNode *)malloc(sizeof(LNode));
if(s==NULL)
return false;
s->data=e;
s->next=p->next;
p->next=s;
return true;
}
出栈-只能在队头操作
bool Pop(LNode *p,ElemType &e){
if(p==NULL || p->next==NULL)
return false;
LNode *q=p->next;
e=q->data;
p=q-next;
free(q);
return true;
}
获取栈顶元素
bool GetTop(LNode *p,ElemType &e){
if(p=NULL || p->next==NULL)
return false;
e=p->next->data;
return true;
}
判空
bool Empty(LinkList L){
if(L->next==NULL)
return true;
else
return false;
}
判满-? 单链表 会满么,好像只用内存不够用的请款吧?
不带头结点操作
入栈--无头结点只能用链表直接操作操作!
已验证-该类操作不能使用LNode *p,使用p指针是将新元素挂不到队列L中的。这里只能使用声明的表头指针L进行头插法
//在p结点后插入一个元素
bool Push(LinkList& L, int e) {//对应单链表的InsertNextNode
if (L == NULL) {
LNode* q;
q = (LNode*)malloc(sizeof(LNode));
if (q == NULL)
return false;
q->data = e;
q->next = L;
L = q;
return true;
}
LNode* s = (LNode*)malloc(sizeof(LNode));
if (s == NULL)
return false;
s->data = e;
s->next = L;
L = s;
return true;
}
出栈-只能在队头操作
bool Pop(LNode* p, int& e) {
if (p == NULL )
return false;
LNode* q = p;
e = q->data;
p = q ->next;
free(q);
return true;
}
获取栈顶元素
bool GetTop(LNode *p,ElemType &e){
if(p=NULL)
return false;
e=p->data;
return true;
}
判空
bool Empty(LinkList L){
if(L==NULL)
return true;
else
return false;
}
2.2、队列
2.2.1、顺序实现
定义
#define MaxSize
typedef struct{
ElemType data[Maxsize];//用静态数组存放队列元素
int front,rear; //一个头指针 一个尾指针
} SqQueue;
初始化
void InitSqQueue(SqQueue &s){
s.front=0;
s.rear=0;
}
判空
bool Empty(SqQueue s){
if(s.rear==s.front)//队空条件
return true;
else
return false;
}
基本操作
入队-- 头指针front 始终队头位置
bool EnQueue(SqQueue &Q,ElemType x){
if(Q.rear==MaxSize)
return false;
Q.data[Q.rear++]=x;//将x插入队尾,队尾指针后移。
return true;
}
2.2.2、顺序循环队列
Q.data[Q.rear]=x;
Q.rear=(Q.rear+1)%Maxsize;
代价:牺牲一个存储单元
//循环队列入队
bool EnQueue(SqQueue Q,ElemType e){
if((Q.rear+1)%MaxSize==Q.front) //队列已满报错
return false;
Q.data[Q.rear]=e;
Q.rear=(Q.rear+1)%MaxSize;//取值范围[0,MaxSize-1] ∴牺牲了列表的最后一个存储单元。
return true;
}
//循环队列出队
bool DeQueue(SqQueue &Q,ElemType &e){
if(Q.rear==Q.front)
return false;
e=Q.data[Q.front];
Q.front=(Q.front+1)%MaxSize;
return true;
}
//获取队头元素
bool GetHead(SqQueue Q,ElemType &e){
if(Q.rear==Q.front)
return false;
e=Q.data[Q.front];
return true;
}
2.2.2.1、方案一
总结:
代价:牺牲一个存储单元
方案一、队列元素个数
(rear+MaxSize-front)%MaxSize;
判满:(Q.rear+1)%Maxsize==Q.front;
判空:Q.front==Q.rear;
不准浪费一个存储空间
2.1.2.2、方案二
方案二、队列个数==size;
判满:size==MaxSize;
判空:size==0;
定义
//定义
#define MaxSize 10
typedef struct{
ElemType data[MaxSize];
int front,rear;
int size; //队列长度,入队:size++;出队:size--
}SqQueue;
//初始化
void InitSqQueue(SqQueue &Q){
Q.front=0;
Q.rear=0;
Q.size=0;
}
基本操作
//入队操作
bool EnQueue(SqQueue &Q,ElemType &e){
if(Q.size==MaxSize)
return false;
Q.data[Q.rear]=e;
//Q.rear=(Q.rear+1);
if(Q.rear==MaxSize-1)
Q.rear=0;
else
Q.rear=(Q.rear+1);
Q.size++;
return true;
}
//出队
bool DeQueue(SqQueue &Q,ElemType &e){
if(Q.size==0)
return false;
e=Q.data[Q.front];
//Q.rear=(Q.rear+1);
if(Q.front==MaxSize-1)
Q.front=0;
else
Q.front=(Q.front+1);
Q.size--;
return true;
}
2.2.2.3、方案三
方案三、
判满:Q.rear==Q.front&&Q.tag==1;//只有添加时队列才会满
判空:Q.rear==Q.front&&Q.tag==0;//只有删除时队列才会为空
遗留问题-怎么计算长度?
定义
#define MaxSize 10;
typedef struct {
ElemType data[MaxSize];
int front,rear;
int tag; //最近进行的是【删除/插入】删除tag=0;插入tag=1;
}SqQueue;
//初始化
void InitSqQueue(SqQueue &Q){
Q.front=0;
Q.rear=0;
tag=0;
}
//插入
bool EnQueue(SqQueue &Q ElemType &e){
if(Q.rear==Q.front&&Q.tag==1)
return false;
Q.data[Q.rear]=e;
if(Q.rear!=MaxSize-1)
Q.rear++;
else
Q.rear=0;
Q.tag=1;
return true;
}
//删除
bool DeQueue(SqQueue &Q ElemType &e){
if(Q.rear==Q.front&&Q.tag==0)
return false;
e=Q.data[Q.front];
if(Q.front!=MaxSize-1)
Q.front++;
else
Q.front=0;
Q.tag=0
return true;
}
2.2.3、队列链式实现
定义
typedef struct LinkNode{//链式队列结点
ElemType data;
struct LinkNode *next;
}*LinkList;
typedef struct{//链式队列
LinkList *front,*rear;//队列的队头和队尾指针
}LinkQueue;
带头结点
初始化
//带头结点初始化
void InitLinkQueue(LinkQueue &Q){
//初始化时 front、rear 都是指向头结点
Q.rear=Q.front=(LinkNode*)malloc(sizeof(LinkNode));
Q.front->next=NULL;
}
判空
bool Empty(LinkQueue Q){
if(Q.rear==Q.front)
return true;
else
return false;
}
入队
bool EnQueue(LinkQueue &Q,ElemType e){
LinkNode *s=(LinkNode *)malloc(sizeof(LinkNode));
if(s==NULL)
return false;
s->data=e;
s->next=NULL;
Q.rear->next=s;//将新节点插入到rear之后
Q.rear=s; //指针始终指向表尾!
return true;
}
出队
bool DeQueue(LinkQueue &Q,ElemType &e){
if(Q.rear==Q.front)
return false;
LinkNode *q=Q.front->next;//头指针初始化时指向头直接点
e=q->data;
Q.front->next=q->next;//修改头结点的 next 指针
if(Q.rear==p) //最后一个队列出队 需要将链表重新初始化。
Q.rear=Q.front;
free(q);
return true;
}
不带头结点
定义
//不带头结点初始化
viod InitLinkQueue(LinkQueue &Q){
//初始化时 front、rear 都是指向NULL
Q.rear=NULL;
Q.front=NULL;
}
判空
bool Empty(LinkQueue Q){
if(Q.front==NULL)
return true;
else
return false;
}
入队
bool EnQueue(LinkQueue &Q,ElemType &e){
LinkDode *s=(LinkDode *)malloc(sizeof(LinkDode));
if(s==NULL)
return false;
s->data=e;
s->ne xt=NULL;
if(Q.front==NULL){//不带头结点的队列,第一个元素入队时需要特别处理
Q.fornt=s;
Q.rear=s;
return true;
}
Q.rear->next=s;
Q.rear=s;
return true;
}
出队
bool DeQueue(LinkQueue &Q,ElemType &e){
if(Q.front=NULL) //队头为空 就是空队列
return false;
LinkNode *q=Q.front;
Q.front=q->next;
if(Q.rear==p) //此次是最后一个队列出队
Q.front=NULL;
Q.rear=NULL;
free(q);
return true;
}
总结:
入队-注意第一个元素入队 队尾入队
出队-注意最后一个元素出队 队头出队
2.3 栈的应用
2.1.1.1、定义
2.3.1 栈的应用-括号匹配
bool bracketChek(char str[],int length){
SqStack S;
InitSqStack(S);
for(int i=0;i<length;i++){
if(str[i]=='('||str[i]=='['||str[i]='{'){
Push(S,str[i]);//扫描左括号 入栈
}
else{
if(StackEmpty(S))//扫描右括号,且当前栈空
return false;
char topElem;
Pop(S,topElem); //栈顶元素出栈
if(str[i]==')' && topElem!='(')
return false;
if(str[i]==']' && topElem!='[')
return false;
if(str[i]=='}' && topElem!='{')
return false;
}
}
return StackEmpty(S);//检索完成全部括号后,栈空说明匹配成功。
}
用栈实现括号匹配:
依次扫描所有字符,遇到左括号入栈,遇到右括号则弹出栈顶元素检查是否匹配。
匹配失败情况:
1、左括号单身
2、右括号单身
3、左右括号不匹配
2.3.2 栈的应用-栈的递归
递归核心思想-想要解决一个问题转化为解决该问题的子问题,而该问题的子问题又包含着一个子问题,逐层嵌套! 即需要逐层解决。
void Function(形参){
if(EndCondition){//递归出口
基本项;
}
else{
递归表达式(递归体):
归纳项;(基本计算项)
形参=expression;//将问题转变为子问题!
function(形参)
}
}
递归调用时,函数调用栈可称为"递归工作栈"
每进入一层递归,就将递归参数所需要信息压入栈顶
导致效率低,大多层递归可能会导致栈溢出;可能包含很多重复计算
每退出一层递归,就从栈顶弹出相应信息
================================================================================================================
eg:斐波那契数列
f ( n ) = { 递归表达式 ( 递归体 ) F i b ( n − 1 ) + F i b ( n − 2 ) , n>1 边界表达式 ( 递归出口 ) 1 , n=1 0 , n=0 f(n) = \begin{cases} 递归表达式(递归体)\\ Fib(n-1)+Fib(n-2), & \text{n>1} \\ 边界表达式(递归出口) \\ 1, & \text{n=1} \\ 0, & \text{n=0} \end{cases} f(n)=⎩ ⎨ ⎧递归表达式(递归体)Fib(n−1)+Fib(n−2),边界表达式(递归出口)1,0,n>1n=1n=0
int Fib(int n){
if(n==0)
return 0;
else if(n==1)
return 1;
else {
return Fib(n-1)+Fib(n-2)
}
}
第五章、树
5.1 树的定义
typedef struct BiTNode{
ElemType data; //数据域
Struct BiTNode *lchild,*rchild; //左,右孩子指针
}BiTNode,*BiTree;
5.2 树的遍历
5.2.1 递归遍历
//先序遍历
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);
}
}
``````cpp
//后序遍历
void PostOrder(BiTree T){
if(T!=NULL){
PostOrder(T->lChild);
PostOrder(T->rChild);
Visit(T);
}
}
5.2.2非递归遍历
1 沿着根的左孩子,依次入栈,直到左孩子为空. 此时说明已经找到了可以输出的结点.
2,栈顶元素出栈并访问:若其右孩子为空,继续执行2.
若右孩子不为空,将右子树执行1.
//中序非递归遍历
void InOrder(BiTree T){
InitStack(S);BiTree p=T;//初始化栈S,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 PreOrder(BiTree T){
InitStack(S);BiTree p=T;//初始化栈S,p是遍历(工作)指针
while(p||!IsEmpty(S)){ //栈不为空或p不为空时循环
if(p){ //一路向左;
Visit(p); //访问当前结点
Push(S,p); //当前结点入栈
p=p->lchild; //左孩子不空,一直向左走
}
else{//出栈,并转向出栈结点的右子树
Pop(S,p);
p=p->rchild;//向右子树走,p赋值为当前结点的右孩子
}
}
}
后序非递归
该非递归实现是三种遍历方法中最难的.
因为在后续遍历中,要保证左孩子和右孩子都已被访问并且左孩子在右孩子前访问才能访问根结点.
后序非递归遍历算法的思路分析::从根结点开始,将其入站,然后沿其左子树一直往下搜索,直到搜索到没有左孩子的结点,但是此时不能出栈并访问,因为如果其有右子树,还需按相同的规则对其右子树进行处理。直至上述操作进行不下去,若栈顶元素想要出栈被访问,要么右子树为空,要右子树刚刚被访问完。(此时左子树早已访问完),这样就保证了正确的访问顺序。
实现方法:1,沿着根的左孩子,依次访问入栈,直到左孩子为空。
2,读栈顶元素:若其右孩子不空且未被访问过,将右子树转执行1;否则栈顶元素出栈并访问。
注意,必须分清返回时时从左子树返回还是从右子树返回的,因此设定一个辅助指针r,指向最近访问过的结点。也可以在结点中增加一个标志域,记录是否已被访问。
void PostOrder(BiTree T){
InitStack(S);
p=Tl
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; //转向右
}else{ //否则,弹出结点并访问
pop(S,p); //将结点弹出
visit(p->data); //访问该结点
r=p; //记录最近访问过的结点
p=NULL; //结点访问完后,重置p指针
}
}//else
}//while
}
//每次出栈访问完一个结点就相当于遍历完该结点为根的子树,需要p置NULL;
5.3 层次遍历
层次遍历,需要借助一个队列。
先将二叉树根节点如队,然后出队,访问出队结点,若它有左子树,则将右子树根节点入队。访问出队结点…
如此反复,直至队列为空。
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);
}
}
求树的高度
非递归
设置变量level 记录当前节点所在的层数,设置变量last指向当前层的最右结点
每次层次遍历出队时与last指针比较,若两者相等,则层数加1,并让last指向下一层的最右结点,直到遍历完成.
//非递归算法
int Btdepth(BiTree T){
if(!T)return 0; //树空,高度为0
int front=-1,rear=-1;
int last=0,level=0;//last 指向当前层的最右结点
BiTree Q[MaxSize];//设置队列Q,元素是二叉树结点,指针且容量足够
Q[++rear]=T;//将根节点入队
BiTree p;
while(front<rear){//队列不空则循环
p=Q[++front];//队列元素出队,即正在访问的结点.
if(p->lchild)
Q[++rear]=p->lchild;
if(p->rchild)
Q[++rear]=p->rchild;
if(front==last){//处理该层的最右接结点
level++;//层数增1
last=rear;//last 指向下层.
}
}
return level;
}
//二叉树的查找:
BTNode BiTreeQuery(BiTree bt, KeyType key){
if(bt==NULL) return NULL;
else if(bt->key==key) return bt;
else{
BiTreeQuery(bt->lchild, key); //往左子树找
BiTreeQuery(bt->rchild, key); //往右子树找
}
}
其他算法
将二叉树的叶子结点,用单链表链接.
算法思想:
1,设置前驱结点指针pre,初始值为空.
2,第一个叶结点由指针head指向,遍历到叶结点时,就将它前驱的rchild指针指向它
3,最后一个叶结点叶结点的rchild 为空.
LinkedList head,pre=NULL;//设置全局变量
LinkedList InOrder(BiTree bt){
if(bt){
InOrder(bt->lchild);//中序遍历左子树
if(bt->lchild==NULL &&bt->rchild==NULL){//叶结点
if(pre==NULL){
head=bt;
pre=bt;
}// 处理第一个叶子结点.
else{
pre->rchild=bt;
pre=bt;
}
}
InOrder(bt->rchild);//中序遍历右子树
pre->rchild=NULL;//设置链表尾部.
}
return head;
}
判断两个树,是否相似.
如果两颗树都为空,则相似. f(t1,t2)=true ;若t1 == t2 == NULL;
如果一颗树为空,另一颗树不为空 . f(t1,t2)=false; 若t1==NULL,t2!=NULL;
f(t1,t2)=f(t1->lchild,t2->lchild)&&f(t1->rchild,t2->rchild); 若 t1与t2都不为空.
bool Similar(BiTree T1,BiTree T2){
//采用递归的算法判断两颗二叉树是否相似
bool leftS,rightS;
if(T1==NULL&&T2==NULL) return true;
else if(T1==NULL||T2==NULL) return false;
else{
lestS=Simlar(T1->lchild,T2->lchild);
righS=Similar(T1->rchild,T2->rchild);
return leftS&&righS;
}
}
判断是否为完全二叉树
如果树为空,则该树为完全二叉树
如果树不为空:先将树的根结点入队。
如果队列不为空则循环
如果结点是非空:则让它的左右孩子入队
如果结点为空:则出对,再判断该节点是不是为空,若不是为空则不是完全二叉树
bool IsComplete(BiTree T){
InitQueue(Q);
if(!T) return true; //空树为满二叉树.
BiTree p;
EnQueue(Q,T);
while(!IsEmpty(Q)){
DeQueue(Q,p);
if(p){ //结点非空,将其左,右子树入队列.
EnQueue(Q,p->lchild);
EnQueue(Q,p->rchild);
}
else{//结点为空,检查其后是否有非空结点
while(!IsEmpty(Q)){
DeQueue(Q,p);
if(p) return false;//结点非空,则二叉树为非完全二叉树
}
}
}
return true;
}
5.4 线索二叉树
5.4.1 线索二叉树的定义
线索二叉树:
lchild | ltag | data | rtag | rchild |
---|
- 线索二叉树的构建
//存储结构描述
typedef struct ThreadNode{
ElemType Data; //数据元素
struct ThreadNode *lchild,*rchild;//左,右孩子指针
int ltag,rtag;//左,右线索标志
}ThreadNode, *ThreadTree;
- 中序线索二叉树的构造
建立二叉链表,将每个结点的左右标志置为0;
遍历二叉链表,建立线索;如果二叉链表p为空,则空操作返回;
对p的左子树建立线索;
对根结点p建立线索;若p没有左孩子,则为p加上前驱线索;
若pre没有右孩子,则pre加上后继线索;
令pre指向刚刚访问过的结点p;对p的右子树建立线索。
线索化的实质就是遍历一次二叉树.
void InThread(ThreadTree &p,ThreadTree &pre){
if(p!=NULL){
InThread(p->lchild,pre);//递归,线索化子树
if(p->lchild==NULL){//左子树为空,建立前驱线索
p->lchild=pre;
p->ltag=1;
}
if(pre!=NULL&&pre->rchild ==NULL){
pre->rchild=p;//建立前驱结点的后继线索
pre->rtag=1;
}
pre=p;//标记当前结点成为刚刚访问过的结点.
InThread(p->rchild,pre);//递归,线索化右子树
}
}
通过中序遍历建立中序线索二叉树的主要过程算法如下:
void CreateInThread(ThreadTree T){
ThreadTree pre=NULL;
if(T){//非空二叉树,线索化
InThread(T,pre);//线索化二叉树
pre->rchild=NULL;//处理遍历的最后一个结点.
pre->rtag=1;
}
}
3. 先序线索二叉树的构造
//先序遍历二叉树,一边便利一边线索化
void PreThread(ThreadTree T){
if(T!=NULL){
visit(T);//先处理根节点
if(T->rtag==0){// lchild 不是前驱线索
PreThead(T->lchild); //防止爱的魔力转圈圈
}
PreThead(T->rchild);
}
}
void visit(ThreadNode *q){
if(q->lchild==NULL){//左子树为空,建立前驱线索
q->lchild=pre;
q->lchild=1;
}
if(pre!=NULL&&pre->rchild==NULL){
pre->rchild=p;//建立前驱结点的后继线索
pre->rtag=1;
}
pre=q;
}
//全局变量 pre,指向当前访问的前驱
ThreadNode *pre=NULL;
王道书上的代码
只有先序线索化才有“爱的魔力转圈圈”的问题
//先序线索化
void PreThread(ThreadTree p,ThreadTree &pre){
if(p!=NULL){//如果p结点不为空
if(P->lchild==NULL){//左子树不为空,建立前驱线索
p->lchild=pre;
p->ltag=1;
}
if(pre!==NULL&&pre->rchild==NULL){
pre->rchild=p;//建立前驱结点的后继线索
pre->rtag=1;
}
pre=p;//标记当前结点为已访问结点
if(p->tag==0)
PreThread(p->lchild,pre);//递归遍历线索化左子树
PreThread(p->rchild,pre);//递归遍历线索化右子树
}//if(p==NULL)
}
void CreatPreThread(ThreadTree T){
ThreadTree *pre=NULL;
if(T!=NULL){
PreThread(T,pre);
if(pre->rchild=NULL){//处理遍历的最后一个结点
pre->rtag=1;
}
}
}
4.后续线索化
void PostThread(ThreadTree p,ThreadTree &pre){
if(p!=NULL){
PostThread(p->lchild,pre);//递归,线索化左子树
PostThread(P->rchild,pre);//递归,线索化右子树
if(p->lchild==NULL){//左子树为空,建立前驱线索
p->lchild=pre;
p->ltag=1;
}
if(pre!=NULL&&pre->rchild==NULL){
pre->rchild=p;//建立前驱结点的后继线索
pre->rtag=1;
}
pre=p;
}
}
//创建后续线索化
void CreatPostThread(ThreadTree T){
ThreadTree *pre=NULL;
if(T!=NULL){//非空二叉树,线索化
PostThread(T,pre);//线索二叉树
if(pre->rchild==NULL)//处理遍历的最后一个结点
pre->rtag=1;
}
}
5.4.2 线索二叉树的遍历
- 中序线索二叉树的遍历
1)求中序序列下的第一个结点:
ThreadNode *Firstnode(ThreadNode *p){
while(p->ltag==0)p=p->lchild;//最左下结点(不一定是叶结点)
return p;
}
求中序列下的最后一个结点
ThreadNode *Lastnode(ThreadNode *p){
while(p->rtag==0) p=p->rchild;
return p;
}
2)求中序线索二叉树中结点p在中序序列下的后继
ThreadNode *Nextnode(ThreadNode *p){
if(p->rtag==0) return Firstnode(p->rchild);
else return p->rchild;
}
求中序线索二叉树中结点p在中序序列下的前驱
ThreadNode *Prenode(ThreadNode *p){
if(p->ltag==0) return Lastnode(p->lchild);
else return p->lchild; //ltag==1 直接返回前驱线索
3, 利用上面两个算法,可以写出不含头节点的中序线序二叉树的算法
void Inoder(ThreadNode *T){
for(ThreadNode *p=FirstNode(T);p!=NULL;p=NextNode(p))
visit(p);
}
对于中序线索二叉树进行逆向中序遍历
void PreInorder(ThreadNode *T){
for(ThreadNode *p=Lastnode(T);P!=NULL;p=Prenode(T)){
visit(p);
}
}
5.5 二叉排序树
二叉排序树的构造
从一颗空树出发,一次输入元素,将他们插入二叉排序树中的合适位置.
void CreatBST(BiTree &T,KeyType str[],int n){
T=NULL;
int i=0;
while(i<n){
BST_Insert(T,Str[i]);
i++;
}
}
二叉排序树的插入算法:
int BST_Insert(BiTree &T,KeyType k){
if(T=NULL){//原树为空,新插入的记录为根节点.
T=(BiTree) malloc(sizeof(BSTNode));
T->key=k;
T->lchilde=T->rchild=NULL;
return 1;//返回1,插入成功
}
else if(k==T->key)//树中存在相同关键字的结点,插入失败.
return 0;
else if(k<T->key)//插入到左子树
return BST_Insert(T->lchild,k);
else //插入到右子树.
return BST_Insert(T->rchild,k);
}
二叉排序树的查找:
BSTNode *BST_Search(BiTree T,ElemType key){
while(T!=NULL && key!=T->data){//若数空或等于根节点值,则结束循环.
if(key<T->data)//小于,则在左子树上查找
T=T->lchild;
else//大于,则在右子树上查找.
T=T->rchild;
}
return T;
}
平衡二叉排序树
调整不平衡
5.6 孩子兄弟表示法
child | data | brother |
---|---|---|
原则左孩子右兄弟 | ||
孩子兄弟表示法的结构
typedef struct CSNode{
ElemType data;//数据域
struct CSNode *firstchild,*nextsibling;//第一个孩子和右兄弟指针
}CSNode,*CSTree;
求以孩子兄弟表示法存储的森林的叶子结点数.
当森林(树)以孩子兄弟表示法存储时,若结点没有孩子(fch==NULL),则它必是叶子,总的叶子结点个数是孩子子树(fch)上的叶子结点和兄弟子树(nsib)上的叶子结点个数之和.
叶子结点=孩子子树的叶子结点+兄弟子树的叶子结点
typedef struct node{
ElemType data;//数据域
struct node *fch,*nsib;//孩子与兄弟域
}*Tree;
int Leaves(Tree t){//计算以孩子兄弟表示法存储的森林的叶子数.
if(t==NULL) return 0;//数空返回0
if(t->fch==NULL)//若结点无孩子,则该结点必是叶子.
return 1+Leaves(t->nsib);//返回叶子结点和其他兄弟子树中的叶子结点数
else //孩子子树和兄弟子树中叶子数之和.
return Leaves(t->fch)+Leaves(t->nsib);
}
以孩子兄弟链表存储结构,请设计递归算法求树的深度.
由孩子兄弟链表表示的树,求高度的算法思想
1, 采用递归算法,若树 为空,高度为零; 否则,高度为第一子女高度加1和兄弟子树高度的大者.
2,其非递归算法使用队列,逐层遍历树,获得数的高度.
int Height(CSTree bt){
//递归求以孩子兄弟链表表示的树的深度.
int hc,hs;
if(bt==NULL)
return 0;
else{//否则,高度取子女高度+1和兄弟子树高度的大者
hc=Height(bt->fistchild);//第一子女树高
hs=Height(bt->nextsibling);//兄弟树高
if(hc+1>hs) return hc+1;
else return hs;
}
}
5.7 哈弗曼树
特点:
1,每个初始结点最终都成为叶结点,并且权值越小的结点到根节点的路径长度越大.
2,构造过程中共新建了N-1个结点(双分支结点),因此哈弗曼树中结点总数为2N-1.
3,每次构造都选择2课树作为新结点的孩子,因此哈弗曼树中不存在度为1的结点.
4,用n个叶子结点构造的哈弗曼树形态可能不唯一,但带权路径长度唯一.
哈夫曼编码:
如果没有一个编码是另一个编码的前缀,则称这样的编码为前缀编码.利用哈弗曼树可以设计出总长度最短的二进制前缀编码.
第六章、 图
有向图的度= 出度+入度
无向图的度:不分出度和入度
无向图的度的算法
1,图若用邻接矩阵存储,只检索第i行或者第i列有几个不为零的元素,则度就是几。
2,若用链接表存储, 则第i个邻接表有多少个结点。则度就是多少。
6.1 图的定义
//图的邻接矩阵的定义
#define MaxVertexNum 100;
typedef char VertexType; //定点数据类型
typedef int EdgeType;
typedef struct{
VertexType Vex[MaxVertexNum]; //顶点表
EdgeType Edge[MaxVertexNum][MaxVertexNum];//邻接矩阵,边表
int vexnum,arcnum; //图的当前顶点和弧数
}MGRaph;
图的邻接表的定义
#define MaxVertexNum 100
typedef struct ArcNode{ //边表结点
int adjvex; //该弧所指向的顶点的位置
struct ArcNode *next; //指向下一条弧的指针
//InforType info; //权值
}ArcNode;
typedef struct VNode{ //顶点表结点
VertexType data; //顶点信息
ArcNode *first; //只想第一条依附该顶点的弧的指针
}VNode,AdjList[MaxVertexNum];
typedef struct{
AdjList vertices; //邻接表
int vexnum,arcnum; //图的顶点数和弧数
}ALGraph; //ALGraph 是以邻接表存储的图类型
6.2 图的遍历
FirstNeighbor(G,x) :求图G中顶点x的第一个临界点,若有则返回顶点号。
若x没有邻接矩阵顶点或图中不存在x,则返回-1;
NextNeighbor(G,x,y) :假设图G中顶点y是顶点x的一个邻接点,
返回除y外顶点x的下一个邻接点的顶点号。
若y是x的最后一个邻接顶点,则返回-1;
6.2.1 广度优先(BFS)遍历 类似于二叉树的层次遍历
伪代码
1,初始化队列Q;
2,访问顶点v;visited[v]=1;顶点v入队列Q;
3,while(队列Q非空)v=队列Q的队头元素出队
w=顶点v的第一个邻接点;
while(w存在)如果w未被访问,则
访问顶点w;
visited[w]=1;
顶点w入队列Q;w=顶点v的下一个邻接点.
bool visited[MAX_VERTEX_NUM]; //访问标记数组
void BFSTraverse(Graph G){ //对图G进行广度优先遍历
for(i=0;i<G.vexnum;++i){
visited[i]=false; //初始化访问标记数组
}
InitQueue(Q); //初始化队列Q
for(i=0;i<G.vexnum;++i){ //从0号定点遍历
if(!visited[i]) //对每一个联通分量调用一次BFS
BFS(G,i); //v1未访问过,从v1开始BFS
}
}
void BFS(Graph G,int v) //从顶点v开始,广度优先遍历图G
{
visit[v]; //访问顶点v
visited[v]=true; //对v做已访问的标记
EnQueue(Q,v); //顶点v入队列Q
while(!isEmpty(Q)){
DeQueue(Q,v); //顶点V出队列
for(w=FirstNeighbor(G,v);w>=0;W=NextNeighbor(G,v,w)){ //检测v所有连接点
if(!visited[w]){ //w为v的尚未访问的顶点
visit(w);
visited[W]=true;
EnQueue(Q,w); //顶点w入队列
}
}
}
}
BFS 可以解决迷宫寻找最短路径的问题.
6.2.2 深度优先(DFS)遍历类似于树的先序遍历
1, 访问顶点V;
2, 从v的未被访问的邻接点找那个选取一个顶点w,从w出发进行深度优先遍历;
3, 重复上述两步,直至图中所有和v有路径相通的顶点都被访问到.
伪代码:
1,访问顶点v, visited[v]=1;
2,w=顶点v的第一个邻接点
3,while(w存在)if(w未被访问)从顶点w出发递归执行该算法;
w=顶点v的下一个邻接点;
bool visited[MAX_VERTEX_NUM]; //访问标记数组
void BFSTraverse(Graph G){ //对图G进行广度优先遍历
for(v=0,v<G.vexnum;++v){
visited[v]=false; //初始化访问标记数组
}
InitQueue(Q); //初始化队列Q
for(v=0;v<Q.vexnum;++v){ //从0号定点遍历
if(!visited[v]) //对每一个联通分量调用一次BFS
DFS(G,v); //v1未访问过,从v1开始BFS
}
}
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)){ //w为V的尚未访问的邻接顶点
if(!visited[w]){
DFS(G,w);
}
}
}
基于邻接矩阵的遍历所得到的的DFS序列和DFS序列是唯一的
基于邻接表的的遍历得到的DFS和BFS序列是不唯一的
第七章、查找
顺序查找
LNode OrderSearch(LinkList L,ElemType key){
LNode *p=L->next;
while(p!=NULL){
if(p->data==key) reutrn p;
else{
p=p->next;
}
}
return NULL;
}
折半查找
思想:1,若顺序表为空,查找失败;否则,执行2
2,将key与顺序表中间位置元素的关键字比较,若相等,查找成功;否则,执行3
3,若key小于(大于)顺序表中间位置元素的关键字,则以中间元素的前半部分(后半部分)作为新的查找表,执行1.
int Binary_Search(SeqList L,ElemType key){
int low=0;high=L.length-1;mid;
while(low<=high){
mid=(high+low)/2;
if(L.elem[mid]==key)
return mid;
else if(L.elem[mid]>key)
high=mid-1;
else
low=mid+1;
}
return -1;
}
B树与B+树
例如:3 阶B树最多有3个分叉。
B树的删除
散列函数
KMP算法
第八章、排序
插入排序
void InsertSort(A[],int n){
int i,j,temp;
for(i=1;i<n;i++){ //将单个元素插入已排好序的序列中
if(A[i]<A[i-1]){//若A[i]关键字小于前驱
temp=A[i];
for(j=i-1;j>=0&&tempt<A[j];--j) //检查所有前面已排好的元素
A[j+1]=A[j];//将所有大于temp的元素都向后挪位
A[j+1]=temp;
}
}
}
希尔排序
void ShellSort(int A[],int n){
int d,i,j;//A[0]只是暂存单元,不是哨兵,当j<=0时,插入位置已到
for(d=n/2,d>=1,d=d/2){ //步长的变化
for(i=d+1;i<=n;++i){
if(A[i]<A[i-d]){ //需将A[i]插入有序增量字表
A[0]=A[i]; //暂存在A[0]
for(j=i-d;j>0;&&A[0]<A[j];j-=d)
A[j+d]=A[j]; //记录后移查找插入位置。
A[j+d]=A[0];
}
}
}
}
冒泡排序
void swap(int &a,int &b){
int temp=a;
a=b;
b=temp;
}
void BubbleSort(int A[],int n){
int i,j;
for(i=0;i<n-1;i++){// 冒泡排序最多经过n-1趟排序
bool flag=false;
for(j=n-1;j>=i,j--){
if(A[j]<A[j-1]){
swap(A[j-1],A{j]);
flag=true;
}
}
if(flag==false){
return;//本趟便利后没有发现转换,说明表已经有序
}
}
}
快速排序
//用第一个元素将待排序序列划分成左右两个部分
int Partition(int A[],int low,int high){
int pivot=A[low]; //第一个元素作为枢轴
while(low<high){
while(low<high&&A[high]>=pivot) --high;//在基准的右半区域找比枢轴小的元素
A[low]=A[high];//将其移到左边区域
while(low<high&&A[low]<=pivot) ++low;//在左边区域找比枢轴大的元素
A[high]=A[low];//将比枢轴大的元素移动到右端。
}
A[low]=pivot;//枢轴元素存放到最终位置。
return low;
}
//快速排序
void QuickSort(int A[],int low,int high){
if(low<high){//递归跳出条件
//Partition()就是用来划分操作,将表A[low...high]划分为满足上述条件的两个子表。
int pivotpos=Partition(A,low,high);//划分
QuickSort(A,low,pivotpos-1);//划分左右字表
QuickSort(A,pivotpos+1,high);
}
}
简单选择排序
void SelectSort(int A[],int n){
for(int i=0;i<n-1;i++){
int min =i; //先设定一个最小值
for(int j=i+1;j<n;j++){ //再用循环依次与之比较,找出最小值
if(A[j]<A[min])min=j;
}
if(min!=i) Swap(A[i],A[min]);
}
}
选择排序、冒泡排序都是n-1趟排序
堆排序
堆的插入和删除
堆的插入:
1,将新元素节点放入堆的末端。
2,若新插入节点小于其父结点或者该结点为根结点,结束;否则3.
3,将新插入结点与其父结点交换,返回2.
堆的删除
被删除元素用堆底元素代替
再根据大/小根堆的规则依次调整。
归并排序
void Merge(int arr[], int tempArr[], int low, int mid, int high) {
//标记左半区第一个未排序的元素
int l_post = low;
//标记右半区域第一个未排序的元素
int r_post = mid + 1;
//临时数组元素的下标
int post = low;
//合并
while (l_post <= mid && r_post <= high) {
if (arr[l_post] < arr[r_post]) { //左半区域第一个剩余元素更小
tempArr[post++] = arr[l_post++];
}
else { //右半区域第一个剩余元素更小
tempArr[post++] = arr[r_post++];
}
}
//合并左半区剩余的元素
while (l_post <= mid) {
tempArr[post++] = arr[l_post++];
}
//合并右半区剩余的元素
while (r_post <= high) {
tempArr[post++] = arr[r_post++];
}
//把临时数组中合并的元素复制回原来的数组;
while (low<high+1)
{
arr[low] = tempArr[low];
low++;
}
}
void MergePartition(int arr[], int tempArr[], int low, int high) {
//如果只有一个元素,那么就不需要继续划分
//只有一个元素的区域,本来就是有序的,只需要被归并即可
if (low < high) {
//找到中间结点
int mid = (low + high) / 2;
//递归划分左半区域
MergePartition(arr,tempArr, low, mid);
//递归划分右半区域
MergePartition(arr, tempArr, mid + 1, high);
//合并
Merge(arr, tempArr, low, mid, high);
}
}
//归并排序
void MergeSort(int arr[], int n) {
//分配一个辅助数组
int* tempArr = (int*)malloc(n * sizeof(int));
if (tempArr) {//辅助数组分配成功
MergePartition(arr, tempArr ,0, n - 1);
free(tempArr);
}
else
{
cout << "Error: Failed to allocate memory!";
}
}
int main() {
int a[10];
for (int i = 0; i < 10; i++)
{
a[i] = rand() % 100;
cout << a[i]<<" ";
}
cout << endl;
MergeSort(a, 10);
for (int i = 0; i < 10; i++)
{
cout << a[i]<<" ";
}
return 0;
}
Merge()算法的时间复杂度为O(n), 空间复杂度为O(n). 元素之间的比较次数最少为Min{len1, len2};最多为: len1+len2-1.
基数排序
时间复杂度O(d(d+r))
d:分配、收集的趟数。
n:数据元素个数。
r:有多少个队列。
来源:王道课程与王道书的结合,也包含自己实现的一些代码(可能有输入错误,逻辑错误等问题)
最后祝各位考研顺利,一研为定!
最后:如有侵权,请告知我。我会在第一时间修改并删除。