数据结构C语言版本(上)

本文涉及的产品
传统型负载均衡 CLB,每月750个小时 15LCU
应用型负载均衡 ALB,每月750个小时 15LCU
EMR Serverless StarRocks,5000CU*H 48000GB*H
简介: 数据结构C语言版本(上)

第一章 绪论

第一节 什么是数据结构?

估猜以下软件的共性:学生信息管理、图书信息管理、人事档案管理。

 数学模型:用符号、表达式组成的数学结构,其表达的内容与所研究对象的行为、特性基本一致。

信息模型:信息处理领域中的数学模型。

 数据结构:在程序设计领域,研究操作对象及其之间的关系和操作。

忽略数据的具体含义,研究信息模型的结构特性、处理方法。

第二节 概念、术语


一、有关数据结构的概念

数据:对客观事物的符号表示。

例:生活中还有什么信息没有被数字化?

身份证,汽车牌号,电话号码,条形代码……

数据元素:数据的基本单位。相当于"记录"。

一个数据元素由若干个数据项组成,相当于"域"。

数据对象:性质相同的数据元素的集合。

数据结构:相互之间存在特定关系的数据集合。

四种结构形式:集合、线性、树形、图(网)状

形式定义:(D,S,P)

D:数据元素的集合(数据对象)

S:D上关系的有限集

P:D上的基本操作集

逻辑结构:关系S描述的是数据元素之间的逻辑关系。

 存储结构:数据结构在计算机中的存储形式。

顺序映象、非顺序映象、索引存储、哈希存储

逻辑结构与存储结构的关系:

逻辑结构:描述、理解问题,面向问题。

存储结构:便于机器运算,面向机器。

程序设计中的基本问题:逻辑结构如何转换为存储结构?


二、有关数据类型的概念

数据类型:值的集合和定义在该值集上的一组操作的总称。

  包括:原子类型、结构类型。

 抽象数据类型(ADT):一个数学模型及该模型上的一组操作。

核心:是逻辑特性,而非具体表示、实现。

课程任务:

学习ADT、实践ADT。

如:线性表类型、栈类型、队列类型、数组类型、广义表类型、树类型、图类型、查找表类型……

实践指导:

为了代码的复用性,采用模块结构。

如:C中的头文件、C++中的类


第三节 ADT的表示与实现

本教材中,算法书写习惯的约定。

数据元素类型ElemType:int,float,char, char[] ……

引用参数 &

算法:

void add(int a,int b,int &c) { c=a+b; }

程序:

void add(int a,int b,int *p_c){ *p_c=a+b; }

第四节 算法的描述及分析


一、有关算法的概念

算法:特定问题求解步骤的一种描述。

1)有穷性  2)确定性  3)可行性

二、算法设计的要求

好算法:

 1)正确性 2)可读性 3)健壮性 4)高效,低存储


三、时间复杂度

事前估算:问题的规模,语言的效率,机器的速度

时间复杂度:在指定的规模下,基本操作重复执行的次数。

n:问题的规模。

 f(n):基本操作执行的次数 

T(n)=O(f(n)) 渐进时间复杂度(时间复杂度)

例:求两个方阵的乘积 C=AB

void MatrixMul(float a[][n],float b[][n],float c[][n])

{ int i,j,k;

for(i=0; i<n; i++) // n+1

for(j=0; j<n; j++) // n(n+1)

{ c[i][j]=0; // nn

for(k=0; k<n; k++) // nn(n+1)

c[i][j]+ = a[i][k] * b[k][j]; // nnn

}

}

时间复杂度:

一般情况下,对循环语句只需考虑循环体中语句的执行次数,可以忽略循环体中步长加1、终值判断、控制转移等成分。

 最好/最差/平均时间复杂度的示例:

例:在数组A[n]中查找值为k的元素。

for(i=0; i<n-1; i++)

if(A[i]==k) return i;


四、常见时间复杂度

按数量级递增排序:

< < < < <

< < <

将指数时间算法改进为多项式时间算法:伟大的成就。


五、空间复杂度

实现算法所需的辅助存储空间的大小。

 S(n)=O(f(n)) 按最坏情况分析。

六、算法举例

例1:以下程序在数组中找出最大值和最小值

void MaxMin(int A[], int n)

{ int max, min, i;

max=min=A[0];

for(i=1; i<n; i++)

if(A[i]>max) max=A[i];

else if(A[i]<min) min=A[i];

printf(“max=%d, min=%d\n”, max, min);

}

若数组为递减排列,比较次数是多少?

if(A[i]>max):n-1次

if(A[i]<min): n-1次

若数组为递增排列,比较次数是多少?

if(A[i]>max):n-1次

if(A[i]<min): 0次

例2:计算f(x)=a0+a1x+a2x2+…+anxn

解法一:先将x的幂存于power[],再分别乘以相应系数。

float eval(float coef[],int n,float x)

{ float power[MAX], f;

int i;

for(power[0]=1,i=1;i<=n;i++)

power[i]=x*power[i-1];

for(f=0,i=0;i<=n;i++)

f+=coef[i]power[i];

return(f);

}

解法二:f(x)=a0+(a1+(a2+……+(an-1+anx)x)… x)x

f(x)=a0+(a1+(a2+(a3+(a4+a5x)x)x)x)x

float eval(float coef[],int n,float x)

{ int i; float f;

for(f=coef[n],i=n-1;i>=0;i–)

f=fx+coef[i];

return(f);

}


五、思考题

1、问:“s=s+ij;”的执行次数?时间复杂度?

for(i=1;i<=n;i++)

if(5i<=n)

for(j=5i;j<=n;j++)

s=s+ij;

2、问:“a[i][j]=x;”的执行次数?时间复杂度?

for(i=0; i<n; i++)

for(j=0; j<=i; j++) a[i][j]=x;


第二章 线性表

线性结构:在数据元素的非空集中,

①存在唯一的一个首元素,

②存在唯一的一个末元素,

③除首元素外每个元素均只有一个直接前驱,

④除末元素外每个元素均只有一个直接后继。


第一节 逻辑结构

形式定义:

Linear_list=(D,S,P)

D = {ai| ai∈ElemSet, i=0,1,2,…,n-1}

S = {<ai-1,ai>| ai-1,ai∈D, i=1,2,…,n-1}

 <ai-1,ai>为序偶,表示前后关系

基本操作P:

①插入、删除、修改,存取、遍历、查找。

void ListAppend(List L, Elem e) ;

void ListDelete(List L, int i) ;

int SetElem(List L, int i, Elem e);

int GetElem(List L, int i, Elem &e);

int ListLength(List L);

void ListPrint(List L);

int LocateElem(List L, Elem e);

②合并、分解、排序

基本操作的用途:

集合的并、交、差运算

  有序线性表的合并、多项式的运算

例:利用线性表LA和LB分别表示集合A和B,求A=A∪B。

void union(List &La,List Lb)

{ int La_len, Lb_len;

La_len=ListLength(La); // 计算表长

Lb_len=ListLength(Lb);

for(i=1; i<=Lb_len; i++)

{ GetElem(Lb, i, e); // 取Lb的第i个元素

if(!LocateElem(La, e)) // 在La中查找e

ListAppend(La, e); // 在La中插入e

}

}


第二节 顺序存储结构

一、概念

逻辑结构中的“前后关系”:物理结构中的“相邻关系”

loc(ai)=loc(a0)+i*sizeof(单个数据元素)

静态顺序存储结构:一维数组。

    注意:第i个元素的下标是i-1

 动态顺序存储结构

#define LIST_INIT_SIZE 100

#define LIST_INCREMENT 10 // 存储空间的分配增量

typedef struct

{ ElemType *elem;

int length; //当前表长

int listsize; //当前已分配的存储空间

}SqList;

二、基本操作:

1、在ai之前插入x:

a0 a1 … ai-1 ai … … an-1

如何移动元素?

a0 a1 … ai-1 x ai … … an-1

void SqList_Delete(SqList A, int i)

{ for(j=i+1; j<A.length; j++)

A.elem[j-1] = A.elem[j];

A.length–;

}


三、效率分析

插入、删除时,大量时间用在移动元素上。

设插入位置为等概率事件,时间复杂度?

例1:增序顺序表的合并,设其中无相同元素

Status SqList_Merge(SqList La,SqList Lb,SqList &Lc)

{ ElemType *pa,*pb,*pc,pa_last,pb_last;

Lc.listsize=Lc.length=La.length+Lb.length;

Lc.elem=(ElemType)malloc(Lc.listsizesizeof(ElemType));

if(!Lc.elem) return ERROR;

pa=La.elem; pb=Lb.elem; pc=Lc.elem;

pa_last=La.elem+La.length-1;

pb_last=Lb.elem+Lb.length-1;

while(pa<=pa_last && pb<=pb_last)

if(*pa<=*pb) { *pc=*pa; pc++; pa++; }

else { *pc=*pb; pc++; pb++; }

while(pa<=pa_last) { *pc=*pa; pc++; pa++; }

while(pb<=pb_last) { *pc=*pb; pc++; pb++; }

return OK;

}

时间复杂度?

现实意义:数据库与日志文件的合并。

例2:无序顺序表的并集A∪B

例3:无序顺序表的交集A∩B

例4:无序顺序表的逆序

例5:剔除顺序表中的某种元素

剔除顺序表中的所有0元素,要求较高效率。

1 2 0 3 4 0 0 0 5 6 7 0 0 8 9

=> 1 2 3 4 5 6 7 8 9

void del(int A[],int n)

{ int i,k; // k是0元素的个数

for(k=0,i=0; i<n; i++)

if(A[i]==0) { k++; n–; }

else A[i-k] = A[i];

}


第三节 线性动态链表

一、概念

每个元素除存储自身信息外,还需存储其后继元素的信息。

 936e0eed1f53443a96149543bec0e8e4.png

7b630ce2631f4a3283b98c2a02e72ce9.png

de2f9b7220f1464d945d72bebb2a9f99.png

结点、指针域、指针(链)

链表、线性链表、头指针


二、动态链表结构

typedef struct LNode

{ ElemType data;

struct LNode next;

}LNODE;

申请结点空间:

LNODE p;

p=(LNODE )malloc(sizeof(LNODE));

释放结点空间:

free§;

1、插入结点

在p之后插入新结点q:

q->next=p->next; p->next=q;

在p之前插入?

 时间复杂度?

各种插入方法:

①首插:插入在首结点前;

②尾插:入在尾结点后。

③有序插:保持结点的顺序,插在表中间;

编程细节:

①首插:修改头指针

②尾插:链表为空时,修改头指针。

③有序插:链表为空时,修改头指针。

链表不空时,可能修改头指针

// 首插

LNODE * LinkList_InsertBeforeHead(LNODE *head, LNODE *newp)

{ newp->next=head; return(newp); }

// 遍历打印

void LinkList_Print(LNODE *head)

{ LNODE *p;

for(p=head; p; p=p->next)

printf(“…”,p->data);

}

// 尾插

LNODE * LinkList_InsertAfterTail(LNODE *head, LNODE *newp)

{ LNODE *p;

if(headNULL) { newp->next=NULL; return(newp); }

for(p=head->next; p->next; p=p->next);

newp->next=NULL; p->next=p;

return(head);

}

简化程序细节的方法:特殊头结点

// 首插(有特殊头结点)

void LinkList_InsertBeforeHead(LNODE *head, LNODE *newp)

{ newp->next=head->next;

head->next=newp;

}

// 尾插(有特殊头结点)

void LinkList_InsertAfterTail(LNODE *head, LNODE newp)

{ LNODE p;

for(p=head; p->next; p=p->next);

newp->next=NULL; p->next=p;

}

2、删除结点

删除p的后继结点:

q=p->next; p->next=q->next; free(q);

删除p?

时间复杂度?

各种删除方法:

①首删除:

②尾删除: 思考

③查找删除:思考

// 首删除(有特殊头结点)

void LinkList_DeleteHead(LNODE *head)

{ LNODE *p;

if(head->nextNULL) return;

p=head->next; head->next=p->next;

free(q);

}


三、单个链表的例程(设以下链表有特殊头结点)

例1、按序号查找:取链表第i个结点的数据元素。i∈[1,n]

// 注意计数次数的边界条件

Status LinkList_GetElemBySID(LNODE *head,int i,ElemType &e)

{ LNODE *p; int j;

for(p=head->next,j=1; p && j<i; j++) p=p->next;

if(pNULL) return ERROR;

e=p->data; return OK;

}

例2、按值查找:取链表中值为e的结点的地址。

LNODE * LinkList_GetElemByValue(LNODE *head, ElemType e)

{ LNODE *p;

for(p=head->next; p; p=p->next)

if(p->datae) break;

return§;

}

例3、释放链表中所有结点。

void LinkList_DeleteAll(LNODE *head)

{ LNODE *p;

while(head)

{ p=head->next; free(head); head=p; }

}

例4、复制线性链表的结点

// 适合有/无特殊头结点的链表

LNODE * LinkList_Copy(LNODE *L)

{ LNODE *head=NULL,*p,*newp,*tail;

if(!L) return(NULL);

for(p=L; p; p=p->next)

{ newp=(LNODE *)malloc(sizeof(LNODE));

if(headNULL) head=tail=newp;

else { tail->next=newp; tail=newp; }

newp->data=p->data;

}

tail->next=NULL; return(head);

}

例5、线性链表的逆序

LNODE * LinkList_Reverse(LNODE *head)  

{ LNODE *newhead,*p;

newhead=(LNODE *)malloc(sizeof(LNODE));

newhead->next=NULL;

while(head->next!=NULL)

{ p=head->next; head->next=p->next; //卸下

p->next=newhead->next; newhead->next=p; //安装

}

free(Head);

return(newhead);

}

例6、利用线性表的基本运算,实现清除L中多余的重复节点。

3 5 2 5 5 5 3 5 6

=> 3 5 2 6

void LinkList_Purge(LNODE *head)

{ LNODE *p, *q, *prev_q;

for(p=head->next; p; p=p->next)

for(prev_q=p,q=p->next; q; )

if(p->dataq->data)

{ prev_q=q->next; free(q); q=prev_q->next; }

else

{ prev_q=q; q=q->next; }

}


四、多个链表之间的例程(设以下链表有特殊头结点)

例1、增序线性链表的合并,设其中无相同元素。

//破坏La和Lb,用La和Lb的结点组合Lc

LNODE *LinkList_Merge(LNODE *La,LNODE *Lb)

{ LNode *pa,*pb,*pctail,*Lc;

Lc=pctail=La; // Lc的头结点是原La的头结点

pa=La->next; pb=Lb->next; free(Lb);

while(pa!=NULL && pb!=NULL)

if(pa->data <= pb->data)

 { pctail->next=pa; pctail=pa; pa=pa->next; }

 else { pctail->next=pb; pctail=pb; pb=pb->next; }

if(pa!=NULL) pctail->next=pa;

else pctail->next=pb;

return(Lc);

}

例2、无序线性链表的交集A∩B

//不破坏La和Lb,

//新链表Lc的形成方法:向尾结点后添加结点

LNODE *LinkList_Intersection(LNODE *La,LNODE *Lb)

{ LNode *pa,*pb,*Lc,*newp,*pctail;

Lc=pctail=(LNODE *)malloc(sizeof(LNODE));

for(pa=La->next; pa; pa=pa->next)

{ for(pb=Lb->next; pb; pb=pb->next)

if(pb->data==pa->data) break;

if(pb)

{ newp=(LNODE *)malloc(sizeof(LNODE));

newp->data=pa->data;

pctail->next=newp; pctail=newp;

}

}

pctail->next=NULL;

return(Lc);

}

作业与上机:(选一)

1、A-B

2、A∪B

3、A∩B

4、(A-B)∪(B-A)


第五节 循环链表

尾结点的指针域指向首结点。

实际应用:手机菜单

遍历的终止条件?

例:La、Lb链表的合并

73caa4a9147c484c9b38074def389085.png

7c457429a48d46e19c95b82304fe009d.png

// 将Lb链表中的数据结点接在La链表末尾

void Connect(LNODE * La,LNODE * Lb)

{ LNODE *pa, *pb;

for(pa=La->next; pa->next!=La; pa=pa->next);

for(pb=Lb->next; pb->next!=Lb; pb=pb->next);

pa->next=Lb->next; pb->next=La;

free(Lb);

}


第六节 双向链表

add46d78e1754ca581633cd74edbe8c4.png

1、在p之后插入结点q

q->rLink=p->rLink; q->lLink=p;

p->rLink=q; q->rLink->lLink=q

在p之前插入结点q ?

2、删除p

(p->lLink)->rLink=p->rLink;

(p->rLink)->lLink=p->lLink;

free§;

删除p的前驱结点?

删除*p的后继结点?


第七节 实例:一元多项式的存储、运算

一、多项式的存储结构

f(x) = anxn +......+a2x2 + a1x + a0

1、顺序结构

int coef[MAX];

f(x) = 14x101+ 5x50 - 3

2、链式结构

typedef struct polynode

{ int coef;

int index;

struct node *next;

}PolyNode;

3、示例

90aadc81f32148189a6ac91772a66132.png

4、结构细节设计

带特殊头结点的单向链表;

结点按指数降序排列;

特殊头结点的index域:多项式的项数。

5、程序框架设计

main()

{ PolyNode *L1,*L2;

L1=PolyNode_Create(); PolyNode_Print(L1);

L2=PolyNode_Create(); PolyNode_Print(L2);

PolyNode_Add(L1,L2); // L1+L2=>L1

PolyNode_Print(L1);

}

void PolyNode_Print(PolyNode *head)

{ PolyNode *p;

printf(“count=%d\n”,head->index); //打印项数

for(p=head->next; p; p=p->next)

printf(“coef=%d,index=%d\n”,p->coef, p->index);

}


二、以插入为基础的算法

1、将newp插入到降序链表head中

void PolyNode_Insert(PolyNode *head, PolyNode *newp);

{ PolyNode *p,pre;

// 定位

for(pre=head,p=pre->next; p; pre=p,p=p->next)

if(p->index <= newp->index) break;

if(p==NULL || p->index < newp->index) //在pre之后插入

{ newp->next=p; pre->next=newp; head->index++; }

else

{ p->coef += newp->coef; //合并同类项

free(newp);

if(p->coef==0) //删除

{ pre->next=p->next; free§; head->index–; }

}

}

2、利用PolyNode_Insert函数,建立有序链表

PolyNode *PolyNode_Create()

{ int i,count;

PolyNode *head,*newp;

scanf(“%d\n”,&count);

head=(PolyNode *)malloc(sizeof(PolyNode));

head->coef=0; head->next=NULL;

for(i=0; i<count; i++)

{ newp=(PolyNode *)malloc(sizeof(PolyNode));

scanf(“%d,%d\n”,&newp->coef,&newp->index);

PolyNode_Insert(head, newp);

}

return(head);

}

3、利用PolyNode_Insert函数,实现多项式加法

// L1+L2=>L1 不破坏L2链表

void PolyNode_Add(PolyNode *L1, PolyNode *L2)

{ NODE *p,*newp;

for(p=L2->next; p; p=p->next)

{ newp=(PolyNode )malloc(sizeof(PolyNode));

newp->coef=p->coef; newp->index=p->index;

PolyNode_Insert(L1,newp);

}

}

时间复杂度?

设L1长度为M,L2长度为N,则O(MN)。


三、两表合并算法

L1+L2=>L1 破坏L2链表

将L2中的每个结点卸下,合并到L1中

dfa69bf932ea41d8993d529a4a460891.png

4439ae03979e4a0793efb82c251a8f7c.png

124e9e70be464371b6e589ed530395d3.png

1363049abbf1411d84ec94358a299b4a.png

void PolyNode_Add(PolyNode *L1, PolyNode L2)

{ PolyNode p1, p1pre; // p1pre 是p1的直接前驱

PolyNode p2, p2next; // p2next是p2的直接后继

p1pre=L1; p1=L1->next; // p1指向L1中第一个数据结点

p2=L2->next; free(L2); // p2指向L2中第一个数据结点

while(p1 && p2)

{ switch(compare(p1->index, p2->index))

{ case ‘>’: // p1指数大于p2指数

p1pre=p1; p1=p1->next; break;

case ‘<’: // p1指数小于p2指数

p2next=p2->next; // 小心①:准备在p1pre后插入p2

p2->next=p1; p1pre->next=p2;

p1pre=p2; // 小心②:保持p1pre和p1的关系

p2=p2next; // p2指向下一个待合并的结点

break;

case ‘=’: // p1和p2是同类项

p1->coef+=p2->coef; // 系数合并

p2next=p2->next; free(p2); // 删除p2

p2=p2next; // p2指向下一个待合并的结点

if(pa->coef==0) // 合并后系数为0

{ p1pre->next=p1->next; free(p1); // 删除p1

p1=p1pre->next; // 保持p1pre和p1的关系

}

} // switch

} // while

if(p2) p1pre->next=p2; // 链上p2的剩余结点

}

时间复杂度?

设L1长度为M,L2长度为N,则O(M+N)。

作业与上机:(选一)

一、多项式加法、减法

二、多项式乘法:ab=>c(建立新表c)

三、任意整数的加法、乘法


第三章 栈与队列

栈与队列:限定操作的线性表。


第一节 栈

一、逻辑结构

1、栈的定义

限定只能在表的一端进行插入、删除的线性表。

14841acd2e374c04bc1b79286edecc86.png

 栈顶top,栈底bottom。

 后进先出LIFO表(Last In First Out)

实例:“进制数转换”、“表达式求值”、“函数调用关系”、“括号匹配问题”、“汉诺塔问题”、“迷宫问题”……


2、基本操作

进栈Push/出栈Pop

取栈顶元素GetTop

判别栈空StackEmpty/栈满StackFull


3、栈的应用背景

许多问题的求解分为若干步骤,而当前步骤的解答,是建立在后继步骤的解答基础上的。=》问题分解的步骤和求解的步骤次序恰好相反。


二、顺序存储结构

动态顺序存储结构:存储空间随栈的大小而变化。

06e01c9e391445b4bda7d6d365915be8.png

SqStack S; //定义一个栈结构


1、初始化栈

Status SqStack_Init(SqStack &S)

{ S.base=malloc(STACK_INIT_SIZE*sizeof(ElemType));

if(!S.base) return(OVERFLOW);

S.top=S.base; S.stacksize=STACK_INIT_SIZE;

return(OK);

}

栈空: S.topS.base

栈满: S.topS.base+S.stacksize


2、进栈

Status SqStack_Push(SqStack &S,ElemType e)

{ if(S.top==S.base+S.stacksize) return(OVERFLOW);

S.top=e; S.top++;

return(OK);

}


3、出栈算法

Status SqStack_Pop(SqStack &S,ElemType &e)

{ if(S.top==S.base) return(UNDERFLOW);

S.top–; e=S.top;

return(OK);

}

缺点:空间浪费

思考:二栈合用同一顺序空间?

471b1398514a417184dccd414c4464d9.png

#define maxsize=100

int stack[maxsize], top0=0, top1=maxsize-1;

dbb20369d64d40b19aa250e9e8fcdd9b.png


三、链栈

629992c0d3264e6dacd3bfa2d6220b10.png

LinkStack S;

初始化: S.topNULL; S.stacksize0

栈 空:S.top==NULL

栈 满:系统内存不够


1、进栈

Status LinkStack_Push(LinkStack &S,ElemType e)

{ LinkNode *p;

p=(LinkNode *)malloc(sizeof(LinkNode));

if(p==NULL) return(OVERFLOW); //上溢

p->data=e;

p->link=S.top; S.top=p;

return(OK);

}


2、出栈

Status LinkStack_Pop(LinkStack &S,ElemType &e)

{ LinkNode *p;

if(S.top==NULL) return(UNDERFLOW);

p=S.top;

e=S.top->data; S.top=S.top->link; free§;

return(OK);

}


3、特殊头结点的讨论

进栈、出栈是最常用的操作

=》链栈的头指针频繁变更

=》参数传递的负担

=》应约定链栈具有特殊头结点。


第二节 栈的应用

1、函数调用栈

f(n)=f(n-1)+f(n-2)

5dcd06e01e834316b4a624994f3fbd00.png


2、将十进制数转换为八进制数。

例如 (1348)10=(2504)8,除8取余的过程:

c1a424fa50d847ad93f19c83e1823bd6.png

void Conversion(int dec,int oct[])

{ InitStack(S);

for(; dec!=0; dec=dec/8)

push(S, dec%8); /* 进栈次序是个位、十位… /

for(i=0; !StackEmpty(S); i++)

oct[i]=pop(S); / 先出栈者是高位,最后是个位 */

}


3、括号匹配的检验

Equal( "((a)(b)) " ) : 0

Equal( “(((a)(b))” ) : 1

Equal( “((a)(b)))” ) : -1

int Equal(char s[])

{ int i=0;

for(i=0; s[i]; i++)

switch(s[i])

{ case ‘(’: push(s[i]); break;

case ‘)’: if(StackEmpty(S)) return(-1); // )多

pop(); break;

}

if(!StackEmpty(S)) return(1); // (多

return(0); // 完全匹配

}


4、进栈出栈的组合问题

已知操作次序:push(1); pop(); push(2); push(3); pop(); pop( ); push(4); pop( ); 请写出出栈序列。


5、中缀表达式求值的算法

如: 1 + 2 *(3 + 4 *(5 + 6))

分析:先乘除,后加减;从左到右;先括号内,再括号外。

9a34786f7d8f4f7dbbdeb49a599a697c.png


①一个运算是否立即执行?必须和“上一个运算符”比较优先级。

3 * 5 + 2

3 - 5 * 2 + 8

②“上一个运算符”存于何处? 运算符栈

③对应的运算数存于何处? 运算数栈

④为保证第一个运算符有“上一个运算符”: Push(OPTR,‘#’)

为保证最后一个运算符能被执行: “3*5+2#”

#:优先级最低的运算符。

⑤运算结果:运算数栈最后的元素。

e15d40bf313b4b089308c4a8dacef044.png

charStack:

类型定义:

基本函数:InitStack1(charStack *);

char GetTop1(charStack *);

void Push1(charStack *, char);

char Pop1(charStack *);

intStack:

类型定义:

基本函数:InitStack2(intStack *);

int GetTop2 (intStack *);

void Push2(intStack , int);

int Pop2(intStack );

float MidExpression_Eval(char Express[]) // Express[]:"35+2#"

{ int i=0; char c,pre_op;

charStack OPTR; intStack OPND;

InitStack1(&OPTR); Push1(&OPTR,‘#’); / 运算符栈 /

InitStack2(&OPND); / 运算数栈 /

while(Express[i]!=‘#’ || GetTop1(&OPTR)!=‘#’)

{ c=Express[i];

if(!InOPTR©)

{ Push2(&OPND, c-‘0’); i++; } / getnum(Express,&i) /

else

{ pre_op=GetTop1(&OPTR);

switch(Precede(pre_op, c))

{ case ‘<’: // pre_op < c

Push1(&OPTR,c); i++; break;

case ‘=’:

Pop1 (&OPTR); i++; break;

case ‘>’: // pre_op > c 执行pre_op

b=Pop2(&OPND); a=Pop2(&OPND); Pop1(&OPTR);

Push2(&OPND,Operate(a,pre_op,b));

}

}

return(GetTop2(&OPND));

}

// 优先级的处理技巧

+ - * / ( ) #

int priority[7][7]={{‘>’, ‘>’, ‘<’, ‘<’, ‘<’, ‘>’, ‘>’}, // +

{‘>’, ‘>’, ‘<’, ‘<’, ‘<’, ‘>’, ‘>’}, // -

{‘>’, ‘>’, ‘>’, ‘>’, ‘<’, ‘>’, ‘>’}, // *

{‘>’, ‘>’, ‘>’, ‘>’, ‘<’, ‘>’, ‘>’}, // /

{‘<’, ‘<’, ‘<’, ‘<’, ‘<’, ‘=’, }, // (

{‘>’, ‘>’, ‘>’, ‘>’, , ‘>’, ‘>’}, // )

{‘<’, ‘<’, ‘<’, ‘<’, ‘<’, , ‘=’}, // #

}

int OpID(char op)

{ switch (op)

{ case ‘+’ : return(0);

case ‘-’ : return(1);

case '’ : return(2);

case ‘/’ : return(3);

case ‘(’ : return(4);

case ‘)’ : return(5);

case ‘#’ : return(6);

}

}

char Precede(char op1, char op2)

{ return(priority[OpID(op1)][OpID(op2)]); }


测试案例:“#”, “3#”,“3+5#”,“3+5+2#”

“35+2#", "3+52#”,“(3+5)2#",

"(3+5)(2+1)#”,“((3+5)/2+1)*2#” …………

作业与上机:

1、表达式求值


第三节 队列

一、逻辑结构

73987fbb9a234b46b85bdaf1fd89e394.png

 只能在一端(队尾rear)插入,在另一端(队头front)删除的线性表。

 先进先出表FIFO(First In First Out)

基本操作:进/出队列

判别队列满/空


队列的应用背景:排队模型。排队问题无处不在,各种服务行业、甚至生产管理中都存在排队问题。


二、链式存储结构

e4413bba0a144ec9857d780e56080231.png


LinkQueue Q;

初始化空队列: Q.frontNULL Q.rearNULL


1、入队列

Status LinkQueue_Enter(LinkQueue &Q, ElemType e)

{ QueueNode *p;

p=(QueueNode *)malloc(sizeof(QueueNode));

if(!p) return(OVERFLOW);

p->data=e; p->link=NULL;

if(Q.front==NULL) Q.front=Q.rear=p;

else { Q.rear->link=p; Q.rear=p; }

return(OK);

}


2、出队列

Status LinkQueue_Leave(LinkQueue &Q,ElemType &e)

{ QueueNode *p;

if(Q.frontNULL) return(UNDERFLOW);

p=Q.front; Q.front=p->link;

if(Q.rearp) Q.rear=NULL;

e=p->data; free§;

return(OK);

}


3、销毁队列

void LinkQueue_Destroy(LinkQueue &Q)

{ QueueNode *p;

while(Q.front)

{ p=Q.front;

Q.front=p->link;

free§;

}

Q.rear=NULL;

}


三、顺序存储结构

动态顺序存储结构:

c075b5b106cb41da9481c62920ce8214.png

SqQueue Q; //定义一个队列结构

rear为下一个进队列元素的位置。

front在队列不空时,指向首元素;

在队列为空时,等于rear。


1、初始化队列

Status SqQueue_Init(SqQueue &Q)

{ Q.base=malloc(QUEUE_SIZEsizeof(ElemType));

if(!Q.base) return(OVERFLOW);

Q.front=Q.rear=0;

return(OK);

}

队列空: Q.front==Q.rear

进队列:(Q.base+Q.rear)=e; Q.rear++;

出队列:e=*(Q.base+Q.front); Q.front++;

51a64f19180145f9b969ab6c45241ec5.png

队列满: Q.rearQUEUE_SIZE

=》假溢出

进队列:Q.rear =(Q.rear +1) % QUEUE_SIZE

出队列:Q.front=(Q.front+1) % QUEUE_SIZE

队列空:Q.frontQ.rear

当队列中有QUEUE_SIZE个元素时:

Q.front==Q.rear

=》必须浪费一个结点空间

队列满:(Q.rear +1) % QUEUE_SIZE == Q.front

77bb646b7a1c46d9b45a84d035020b89.png


2、入队列

Status SqQueue_Enter(SqQueue &Q,ElemType e)

{ if((Q.rear +1) % QUEUE_SIZE==Q.front) return(OVERFLOW);

*(Q.base+Q.rear)=e;

Q.rear=(Q.rear +1) % QUEUE_SIZE;

return(OK);

}


3、出队列

Status SqQueue_Leave(SqQueue &Q,ElemType &e)

{ if(Q.rear==Q.front) return(UNDERFLOW);

e=*(Q.base+Q.front);

Q.front=(Q.front+1) % QUEUE_SIZE;

return(OK);

}


4、元素计数

(Q.rear-Q.front+QUEUE_SIZE)% QUEUE_SIZE

值的范围0 …… QUEUE_SIZE-1

思考:一定要浪费一个结点空间?

利用一个标志变量Q.flag (0:非满,1:非空)。

c13268bd4b3b40419e6e4a62d376b4f2.png

int Empty(SqQueue Q)

{ if(Q.frontQ.rear && Q.flag0) return(1);

return(0);

}

int Full(SqQueue Q)

{ if(Q.frontQ.rear && Q.flag1) return(1);

return(0);

}


1、初始化队列

Status SqQueue_Init(SqQueue &Q)

{ Q.front=Q.rear=0; Q.flag=0; }


2、入队列

Status SqQueue_Enter(SqQueue &Q,ElemType e)

{ if(Full(Q)) return(OVERFLOW);

…………

Q.flag=1; //非空

return(OK);

}


3、出队列

Status SqQueue_Leave(SqQueue &Q,ElemType &e)

{ if(Empty(Q)) return(UNDERFLOW);

…………;

Q.flag=0; //非满

return(OK);

}

第四节 队列的实例:离散事件的模拟


一、排队问题

加油站的问题:

一个加油站只有一台加油机,平均为每辆车加油需要5分钟,假定一个小时内,有20辆车随机进入加油站加油,计算每辆车的平均等待时间.

银行营业所应设置几个柜台?

1、设置N柜台时,计算顾客的平均等待时间;

2、选择合适的N值。

二、两个栈组合出一个队列

制定两个栈与一个队列的对应规则:

92b85de2d3934f82b90cb943eef05247.png

stack s1,s2;

void Enter(ElemType e)

{ Push(s1,e); }


ElemType Leave()

{ ElemType e;

if( !Empty(s2) ) return( Pop(s2) );

while( !Empty(s1) )

Push(s2, Pop(s1));

return( Pop(s2) );

}


相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
14天前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
90 9
|
13天前
|
存储 搜索推荐 算法
【数据结构】树型结构详解 + 堆的实现(c语言)(附源码)
本文介绍了树和二叉树的基本概念及结构,重点讲解了堆这一重要的数据结构。堆是一种特殊的完全二叉树,常用于实现优先队列和高效的排序算法(如堆排序)。文章详细描述了堆的性质、存储方式及其实现方法,包括插入、删除和取堆顶数据等操作的具体实现。通过这些内容,读者可以全面了解堆的原理和应用。
57 16
|
13天前
|
C语言
【数据结构】二叉树(c语言)(附源码)
本文介绍了如何使用链式结构实现二叉树的基本功能,包括前序、中序、后序和层序遍历,统计节点个数和树的高度,查找节点,判断是否为完全二叉树,以及销毁二叉树。通过手动创建一棵二叉树,详细讲解了每个功能的实现方法和代码示例,帮助读者深入理解递归和数据结构的应用。
62 8
|
16天前
|
存储 C语言
【数据结构】手把手教你单链表(c语言)(附源码)
本文介绍了单链表的基本概念、结构定义及其实现方法。单链表是一种内存地址不连续但逻辑顺序连续的数据结构,每个节点包含数据域和指针域。文章详细讲解了单链表的常见操作,如头插、尾插、头删、尾删、查找、指定位置插入和删除等,并提供了完整的C语言代码示例。通过学习单链表,可以更好地理解数据结构的底层逻辑,提高编程能力。
44 4
|
17天前
|
存储 C语言
【数据结构】顺序表(c语言实现)(附源码)
本文介绍了线性表和顺序表的基本概念及其实现。线性表是一种有限序列,常见的线性表有顺序表、链表、栈、队列等。顺序表是一种基于连续内存地址存储数据的数据结构,其底层逻辑是数组。文章详细讲解了静态顺序表和动态顺序表的区别,并重点介绍了动态顺序表的实现,包括初始化、销毁、打印、增删查改等操作。最后,文章总结了顺序表的时间复杂度和局限性,并预告了后续关于链表的内容。
49 3
|
17天前
|
存储 算法 C语言
C语言数据结构(2)
【10月更文挑战第21天】
|
16天前
|
C语言
【数据结构】双向带头循环链表(c语言)(附源码)
本文介绍了双向带头循环链表的概念和实现。双向带头循环链表具有三个关键点:双向、带头和循环。与单链表相比,它的头插、尾插、头删、尾删等操作的时间复杂度均为O(1),提高了运行效率。文章详细讲解了链表的结构定义、方法声明和实现,包括创建新节点、初始化、打印、判断是否为空、插入和删除节点等操作。最后提供了完整的代码示例。
37 0
|
5天前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
15 1
|
8天前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
|
11天前
|
存储 JavaScript 前端开发
执行上下文和执行栈
执行上下文是JavaScript运行代码时的环境,每个执行上下文都有自己的变量对象、作用域链和this值。执行栈用于管理函数调用,每当调用一个函数,就会在栈中添加一个新的执行上下文。

热门文章

最新文章