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

本文涉及的产品
应用型负载均衡 ALB,每月750个小时 15LCU
网络型负载均衡 NLB,每月750个小时 15LCU
传统型负载均衡 CLB,每月750个小时 15LCU
简介: 数据结构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)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
22天前
|
算法 数据处理 C语言
C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合
本文深入解析了C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合,旨在帮助读者掌握这一高效的数据处理方法。
34 1
|
1月前
|
存储 算法 搜索推荐
【趣学C语言和数据结构100例】91-95
本文涵盖多个经典算法问题的C语言实现,包括堆排序、归并排序、从长整型变量中提取偶数位数、工人信息排序及无向图是否为树的判断。通过这些问题,读者可以深入了解排序算法、数据处理方法和图论基础知识,提升编程能力和算法理解。
45 4
|
1月前
|
存储 机器学习/深度学习 搜索推荐
【趣学C语言和数据结构100例】86-90
本文介绍并用C语言实现了五种经典排序算法:直接插入排序、折半插入排序、冒泡排序、快速排序和简单选择排序。每种算法都有其特点和适用场景,如直接插入排序适合小规模或基本有序的数据,快速排序则适用于大规模数据集,具有较高的效率。通过学习这些算法,读者可以加深对数据结构和算法设计的理解,提升解决实际问题的能力。
43 4
|
1月前
|
存储 算法 数据处理
【趣学C语言和数据结构100例】81-85
本文介绍了五个经典算法问题及其C语言实现,涵盖图论与树结构的基础知识。包括使用BFS求解单源最短路径、统计有向图中入度或出度为0的点数、统计无向无权图各顶点的度、折半查找及二叉排序树的查找。这些算法不仅理论意义重大,且在实际应用中极为广泛,有助于提升编程能力和数据结构理解。
39 4
|
1月前
|
算法 数据可视化 数据建模
【趣学C语言和数据结构100例】76-80
本文介绍了五种图论算法的C语言实现,涵盖二叉树的层次遍历及广度优先搜索(BFS)和深度优先搜索(DFS)的邻接表与邻接矩阵实现。层次遍历使用队列按层访问二叉树节点;BFS利用队列从源节点逐层遍历图节点,适用于最短路径等问题;DFS通过递归或栈深入图的分支,适合拓扑排序等场景。这些算法是数据结构和算法学习的基础,对提升编程能力和解决实际问题至关重要。
48 4
|
1月前
|
存储 算法 vr&ar
【趣学C语言和数据结构100例】71-75
本文介绍了五个C语言数据结构问题及其实现,涵盖链表与二叉树操作,包括按奇偶分解链表、交换二叉树左右子树、查找节点的双亲节点、计算二叉树深度及求最大关键值。通过递归和遍历等方法,解决了理论与实际应用中的常见问题,有助于提升编程能力和数据结构理解。
37 4
|
1月前
|
存储 算法 C语言
【趣学C语言和数据结构100例】66-70
本书《趣学C语言和数据结构100例》精选了5个典型的数据结构问题及C语言实现,涵盖链表与数组操作,如有序集合的集合运算、有序序列表的合并、数组中两顺序表位置互换、三递增序列公共元素查找及奇偶数重排。通过详细解析与代码示例,帮助读者深入理解数据结构与算法设计的核心思想,提升编程技能。
33 4
|
24天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
44 5
|
22天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
54 1
|
1月前
|
存储 机器学习/深度学习 算法
【趣学C语言和数据结构100例】61-65
本文介绍了五个关于C语言和数据结构的经典问题及其实现方法,涵盖查找链表共同后缀、删除重复节点、重新排列链表元素、合并有序链表以及特定条件下的链表排序。每个问题通过具体的算法设计,不仅展示了链表操作的灵活性,还强调了效率优化的重要性。通过这些问题的探讨,读者可以深入理解链表的基本操作和高级应用,提升解决实际问题的能力。
46 4