数据结构总复习资料(完整版)(精华版)
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
2018 数据结构总复习
第一章概论
1.1 数据结构的定义和分类
1. 数据结构的定义
数据结构是一门研究非数值计算的程序设计问题中计算机的操作对象以及它们之间的关系和操作的学科。
2. 数据结构包括的内容
(1)逻辑结构:数据元素之间的逻辑关系。
(2)存储结构:数据元素及其关系在计算机存储器内的表示。
(3)操作:数据的运算(检索、排序、插入、删除、修改)。
1.2 为什么学习数据结构
1. 学习数据结构的作用
(1)计算机内的数值运算依靠方程式,而非数值运算(如表、树、图等)则要依靠数据结构。
(2)同样的数据对象,用不同的数据结构来表示,运算效率可能有明显的差异。
(3)程序设计的实质是对实际问题选择一个好的数据结构,加之设计一个好的算法。
而好的算法在很大程度上取决于描述实际问题的数据结构。
2. 电话号码查询问题
(1)要写出好的查找算法,取决于这张表的结构及存储方式。
(2)电话号码表的结构和存储方式决定了查找(算法)的效率。
1.3 算法的概念和特点
1. 算法的概念和特点
算法是由若干条指令组成的有穷序列,具有以下特点:
(1)输入:具有0 个或多个输入的外界量。
(2)输出:至少产生 1 个输出。
(3)有穷性(4)确定性(5)可行性:每一条指令的执行次数必须是有限的。
:每条指令的含义都必须明确,无二义性。
:每条指令的执行时间都是有限的。
2. 算法与程序的区别
(1)一个程序不一定满足有穷性,但算法一定。
(2)程序中的指令必须是机器可执行的,而算法无此限制。
(3)一个算法若用机器可执行的语言来描述,则它就是一个程序。
1.4 算法分析
1. 时间复杂度
算法中基本操作重复执行的次数是问题规模n 的某个函数,用T(n) 表示,若有某个辅助函数f(n) ,使得当n 趋近于无穷大时,的极限值为不等于零的常数,则称f(n) 是T(n) 的同数量级函数。
T(n) / f(n)
记作T(n)=O(f(n)) ,称O(f(n)) 为算法的渐近时间复杂度,简称时间复杂度。
算法效率的度量,采用时
间复杂度。
常见函数的时间复杂度按数量递增排列及增长率:
常数阶对数阶线性阶O(1)
O(log 2n) O(n)
线性对数阶O(n log 2n)
平方阶O(n )
立方阶O(n3)
2
k
k 次方阶O(n )
指数阶O(2 ) n
2. 空间复杂度
空间复杂度是指算法在计算机内执行时所需存储空间的度量,记作:。
S(n) = O(f(n))
3. 算法分析的目的
目的在于选择合适算法和改进算法
1.5 例题
例1:
for ( i=1; i<n; i++ )
{ y = y+1; // 语句 1
for ( j=0; j<=(2*n); j++ )
x++; 语句2
//
}
解:语句 1 频度为(n-1) ;语句2 频度为,因此时间复杂度。
(n-1)*(2n+1)=2n2-n-1 T(n)=2n2-2=O(n2)
例2:
i=1;
while (i<=n)
i=i*2;
// 语句1
// 语句 2
1;设语句
解:语句 1 频度为 2 频度为f(n) ,则有2f(n)<=n。
,即,去极大值,f(n)=log2n ,f(n)<=log2n
因此时间复杂度T(n)=1+log2n=O(log2n)
第二章线性表
2.1 线性表的概念和运算
1. 线性表的概念
线性表是n (n ≥0) 个类型相同的数据元素组成的有限序列。
其中数据元素的个数n 为线性表的长度,当n=0 时称为空表。
2. 线性表的特点
对于非空的线性表,有且仅有一个开始结点和一个终端结点;开始结点没有直接前趋,有且仅有一个直接后继;终端结点
直接后继。
没有直接后继,有且仅有一个直接前趋;其余任何结点有且仅有一个直接前趋和一个3. 线性表的计算
(1) 置空表
(2) 求长度
(3) 取结点NULL。
SETNULL(L) :将线性表L 置为空表。
LENGTH(L) :返回是线性表L 中结点的个数。
:当 1 ≤i ≤LENGTH(L)时,返回线性表L 中的第i 个结点,否则返回GET(L, i )
(4) 定位存在多个值为LOCATE(L, x) :当线性表L 中存在一个值为x 的结点时,结果是该结点的位置;若表L 中x 的结点,则返回首次找到的结点位置;若表L 中不存在值为x 的结点,则返回一个特殊值
表示值为x 的结点不存在。
(5) 插入INSERT(L,
n 是原表L 的长度。
x, i):在线性表L的第i个位置插入一个值为x 的新结点。
这里1 ≤i ≤ n+1 ,
(6) 删除DELETE(L, i) :删除线性表L 的第i 个结点。
这里 1 ≤i ≤ n ,n 是原表L 的长度。
2.2 线性表的存储结构
1. 顺序存储:
(1)定义:把逻辑上相邻的数据元素存储在物理上相邻的存储单元中的存储结构。
简言之,逻辑上相邻,物理上也相邻。
(2)顺序存储方法:用一组地址连续的存储单元依次存储线性表的元素,可通过数组来实现。
(3)地址计算:设首元素a1 的存放地址为LOC(a1)(称为首地址),设每个元素占用存储空间(地址
长度)为L 字节,则地址计算公式:。
LOC(ai) = LOC(a1) + ( i-1)*L
(4)结构定义:
#define MAXSIZE 1024 typedef int datatype; typedef struct
{ // 线性表的最大长度
// 线性表数据元素类型
datatype
int last; }sequenlist; data[MAXSIZE];
指示线性表的终端结点在表中的下标值//
2. 链式存储:
( 1)特点:用一组 任意 的存储单元存储线性表的数据元素,利用 指针 实现了用不相邻的存储单元存
放逻辑上相邻的元素,每个数据元素
( 2)头指针、头节点、开始节点
ai ,除存储本身信息外,还需存储其直接后继的信息。
头指针 是指向链表中 第一个 结点(或为头结点或开始结点) 的指针, 单链表可由一个头指针唯一确定。
头结点 是在链表的 开始结点之前附设的一个结点 ; 数据域内只放空表标志和表长等信息
a1 的结点。
;
开始结点 是指链表中存储线性表 ( 3)空表的表示
无 头结点时,当 头指针的值为空 第一个数据元素 时表示空表; 有 头结点时,当 头结点的指针域为空 ( 4)结构定义 时表示空表。
// 线性表数据元素类型
typedef int datatype; typedef struct node { // 数据域 // 指针域
datatype data;
struct node } linklist;
*next;
3. 存储结构比较
( 1)优缺点
顺序存储的 优点 是存储密度大 查找这样的静态操作。
( = 1) ,存储空间利用率高。
缺点 是插入或删除元素时不方便。
适合做
链式存储的 优点 是插入或删除元素时很方便, 适合做做插入、删除这样的动态操作。
使用灵活。
缺点 是存储密度小 (<1 ),存储空间利用率低。
( 2)线性表的顺序存储与链式存储对线性表的运算比较
顺序存储 时,相邻数据元素的存放地址也相邻(逻辑与物理统一) 必须是连续的。
;要求内存中可用存储单元的地址
链式存储 时,相邻数据元素可随意存放,但所占存储空间分两部分,一部分存放结点值,另一部分存 放表示结点间关系的指针。
( 3)时间复杂度和存储密度比较
顺序存储存储密度 删除时时间复杂度为 =1,链式存储 <1。
顺序表中访问任意一结点的时间复杂度均为 O(1) ,但是在插入和
O(n) 。
链表时间复杂度为 O(n) 。
4. 单链表操作
( 1)查找
void findValue(Linklist *head ,int
x){ Linklist *t;
t = head->next; while(t!=NULL && t->data !=x)
t=t->next; if(t->data == x)
printf(" 成功找到 \n"); else
printf("
没有找到 \n");
}
( 2)插入
void insert(Linklist *head,int x){
Linklist *t,*p;
p = (Linklist*)malloc(sizeof(Linklist));
p->data = x;
t = head;
while(t->next != NULL && t->next->data < x) t = t->next;
if(t == head){
p->next = head->next;
head->next = p;
}else if(t->next == NULL){
t->next = p;
p->next = NULL;
}else{
p->next = t->next;
t->next = p;
}
}
(3)删除
void dele(Linklist
*head){Linklist
p ,q; p=head;
while (p->next !=NULL){
if (p->data !=p->next->data) p=p->next;
else
{ q=p->next; p->next=q->next; free(q);} }
}
(4)逆置
void reverse(Linklist
*head){Linklist
*s,*t,*p;
p = head;
s = p->next;
while(s->next != NULL)
{ t = s->next;
s->next = p;
p = s;
s = t;
}
s->next = p;
head->next->next = NULL;
head->next = s;
2.3 例题
例1:一个一维数组M,下标的范围是0 到9,每个数组元素用相邻的 5 个字节存储。
存储器按字节编址,设存储数组元素M[O]的第一个字节的地址是98,则M[3] 的第一个字节的地址是。
113
例2:在一个长度为个元素(或删除第
n 的顺序表中向第i 个元素(1≤ i ≤n+1)之前插入一个新元素时,需要向后移动n-i+1 i 个元素,需要向前移动个元素)。
n-i
例3:在单链表中,若*p 结点不是末尾结点,在其前或后插入*s 结点或删除结点的操作是?
解:在其前插入
t ;
在其后插入*s 结点:s->next= p->next ; p->next=s; t=p->data; p->data=s->data ; s->data= *s 结点:s->next=p->next; p->next=s;
删除其前结点:删除其后结点:需要利用遍历
q = p->next; p->next=q->next; free(q);
删除自身接结点:q=p->next; p->data= q->data ; p->next=q->next ; free(q);
例4:在线性表的下列存储结构中,读取指定序号的元素花费时间最少的是顺序结构。
第三章栈和队列
3.1 栈和队列的基本概念
1. 栈的概念
栈是只允许在端叫栈底(bottum) 同一端进行插入和删除操作的线性表。
允许进行插入和删除的一端叫栈顶(top) ,另一,栈中无数据元素时,称为空栈。
具有先进后出(FILO)或后进先出(LIFO)的特点。
2. 栈的定义
(1)顺序栈
typedef int datatype;
# define MAXSIZE 100
typedef struct{ (2)链栈
typedef int datatype;
typedef struct node{
datatype data;
struct node * next;
} linkstack;
linkstack * top;
top 是栈顶指针,它唯一地确定一个链栈。
top=NULL 时,该链栈为空栈。
链栈中的结点是动态
datatype data[MAXSIZE];
int top;
} seqstack;
seqstack *s;
产生的,无需考虑上溢问题。
3. 顺序栈操作
(1)判断栈空
int EMPTY(seqstack *s) {
–>top>=0);
return(!s
}
(2)置空栈
void SETNULL(seqstack *s) {
–>top=-1;
s
}
(3)判断栈满
int FULL(seqstack *s) {
return(s –>top==MAXSIZE-1);
}
(4)进栈
seqstack * PUSH(seqstack *s
if (FULL(s))
,datatype x) {
“ stack overflow ” ); return NULL; } { printf(
s–>top++; s –>data[s –>top]=x;
return s;
}
(5)出栈
datatype POP(seqstack *s)
{ if (EMPTY(s)) {
printf( “ stack underflow
return NULL;
}
s–>top--;
return(s –>data[s –>top+1]);
}
(6)取栈顶
datatype TOP(seqstack *s)
{ if (EMPTY(s)) {
printf( “ stack is empty
return NULL;
}
return (s –>data[s –>top]);
}
”); ”);
4. 链栈操作
(1)进栈
linkstack *PUSHLSTACK(linkstack *top, datatype x)
{ linkstack *p;
p=(linkstack *)malloc(sizeof(linkstack));
p->data =x;
p->next =top;
返回新栈顶指针
return p; /* */
}
(2)出栈
linkstack *POPLSTACK(linkstack *top, datatype *datap) {
linkstack *p;
if (top==NULL)
{ printf( “ under flow ” ); return NULL;}
datap =top->data; /*
p=top;
top =top->next;
free(p);
栈顶结点数据存入*datap */
返回新栈顶指针
return top; /* */
}
5. 队列的概念
队列(Queue) 也是一种运算受限的线性表。
只允许在表的一端进行插入,而在另一端进行删除。
允许删除的一端称为队头(front) ,允许插入的一端称为队尾(rear) 。
具有先进先出(FIFO)的特点。
6. 队列的定义
(1)顺序队列
#define MAXSIZE 100
typedef struct{
datatype data[MAXSIZE];
int front;
int rear;
}sequeue;
sequeue * sq; (2)链式队列
typedef struct
queuenode{datatyp
e data;
struct queuenode *next;
}QueueNode;
typedef struct{
QueueNode *front;
QueueNode *rear;
7. 循环队列
(1)假上溢
在入队和出队的操作中,头尾指针只增加不减小,致使被删除元素的空间永远无法重新利用。
尽管队列中实际的元素个数远远小于向量空间的规模,但也可能由于尾指针巳超出向量空间的上界而不能做入队
操作,该现象称为
(2)循环队列
假上溢。
为了解决假上溢问题,引入了循环队列的概念。
在循环队列中进行出队、入队操作时,头尾指针仍要
加1,朝前移动。
只不过当头尾指针指向0。
(3)队空队满问题
入队时:尾指针向前追赶头指针,
向量上界( MaxSize-1 ) 时,其加 1 操作的结果是指向向量的下界出队时:头指针向前追赶尾指针,故队空和队满时头尾指针均相等,
无法通过来判断队列“空”还是“满”。
sq->front= =sq-> rear
解决此问题的方法至少有两种:
1、另设一个布尔变量以区别队列的空和满;
2、少用一个元素空间为代价,入队前,测试尾指针(4)常用操作
,若相等则认为队满。
sq-> rear+1==sq->front
队空判断队满判断入队::
:
sq->front == sq-> rear
sq-> front ==(sq-> rear + 1) % maxSize sq-> rear = (sq-> rear + 1) % maxSize
出队:求队长:sq-> front = (sq-> front + 1) % maxSize (sq-> rear - sq-> front+maxSize)%maxSize
8. 循环队列操作
(1)入队
①检查队列是否已满,若队满,则进行溢出错误处理。
②将队尾指针后移一个位置(即加
③将新元素赋给队尾指针所指单元。
1),指向下一单元。
Status EnQueue (SqQueue *Q, ElemType e){
if ( (Q->rear+1)%MAXQSIZE == Q->front )
// 队满上溢
return(ERROR);
Q->rear=(Q->rear+1)%MAXQSIZE;
Q->data[Q->rear]=e;
return(True);
}
(2)出队
①检查队列是否为空,若队空,则进行下溢错误处理。
②将队首指针后移一个位置(即加
③取队首元素的值。
Status DeQueue (SqQueue *Q)
{ if (Q->rear== Q->front)
1)。
// 队空下溢
return(NULL);
Q->front=(Q->front+1)%MAXQSIZE;
return(Q->data[Q->front]);
}
(3)置空队
Q->front=Q->rear= MAXQSIZE-1;
(4)取队头
datatype GetHead(SqQueue *Q )
{ if (Q->front==Q->rear)
// 队空
return(NULL);
return (Q->data[(Q->front+1)%MAXQSIZE] );
}
(5)判断队空
int QueueEmpty(SqQueue
*Q ){ if (Q->front==Q-
>rear)
return (True);
else
return (False);
9. 链式队列操作
(1)置空
void InitQueue(LinkQueue *Q) {
Q.front=Q.rear=(queuenode *)malloc(sizeof(queuenode ));
Q.front->next=Q.rear->next=NULL;
}
(2)判断队空
int QueueEmpty(LinkQueue *Q) {
return (Q.front->next= =NULL &&Q.rear->next= =NULL);
}
(3)入队
void EnQueue(LinkQueue *Q, datatype
x){ QueueNode *p;
p=(QueueNode * )malloc(sizeof(QueueNode));
p–>data=x;
p–>next=NULL;
Q->rear –>next=p;
Q->rear=p;
}
(4)出队
DeQueue(linkqueue *Q)
{ linkqueue *p;
datatype x;
if (EMPTY(Q))
return NULL;
p = Q->front->next;
–>next;
Q->front->next = p
if (p->next==NULL)
Q->rear = Q->front;
x = p->data;
free(p);
return x;
}
3.2 栈和队列的应用
1. 递归函数
递归函数又称为自调用函数,
n!=1,
FACT(n) =1,
n>1
C 语言描述如下:
int FACT(int n) {
if (n==1)
return (1);
else 它的特点:在函数内部可以直接或间接地调用函数自己。
例如阶乘函数:
可以被描述为
!n>1 ,
n=1&n*n-1 ,
n=1&n*FACTn-1 !,
return (n*FACT(n-1)); }
2. 算法表达式求值
计算机系统在处理表达式前,先设置两个栈:操作数栈(OPRD):存放处理表达式过程中的操作数;运
算符栈(OPTR):存放处理表达式过程中的运算符。
开始时,在运算符栈中先在栈底压入一个表达式的结束
符“#”。
(1)中缀表示法计算机系统在处理表达式时,从左到右依次读出表达式中的各个符号(操作数或运算符),每读
出一个符号
①如果ch 后,根据运算规则做如下处理:
ch 是操作数,则将其压入操作数栈OPR D,并依次读取下一个符号。
②若ch 是运算符,则:
A、若读出的运算符的优先级大于一个符号。
OPTR栈顶的运算符优先
级,
则将其压入OPT R,并依次读下
B、若读出的是“#”,且OPT R栈顶的运算符也是“
在OPRD的栈顶位置。
C、若读出的是“(”,则将其压入OPT R。
D、若读出的是“)”,则:
#”,则表达式处理结束,最后的计算结果
若OPT R栈顶不是“ ( ”,则从OPR D连续退出两个操作数,从OPTR中退出一个运算符,ch 继续与OPTR栈顶元素
然后作相应的运算,并将运算结果压入
行比较。
OPR D,然后返回a) ,让若OPT R栈顶为“ ( ”,则从OPT R退出“ ( ”,依次读下一个符号。
E、若读出的运算符的优先级小于OPTR栈顶运算符的优先级,则
从OPRD连续退出两个操作
数,从OPTR中退出一个运算符,然后作相应的运算,将运算结果压入
进行比较。
(2)波兰表示法和逆波兰表示法OPRD。
返回(2) 继续OPTR栈顶元
素
以5 + ( 6 –4 / 2 ) * 3 。
为例,波兰表示法:逆波兰表示法:
+ 5 * - 6 / 4 2 3 5 6 4
2 / -
3 * +
运算时按从左到右的顺序进行,不需要括号。
在计算表达式时,可设置一个栈,从左到右扫描后缀表达式,每读到一个操作数就将其压入栈中;每读到一个运算符时,则从栈顶取出两个操
作数运算,并将结果压入栈中,一直到后缀表达式读完。
最后栈顶就是计算结果。
3. 括号匹配
#include<stdio.h> #define maxsize 100 typedef int datatype; void main(){
char ss[maxsize];
build();
printf(" 请输入要测试的算数表达式
scanf("%s",ss);
if(check(ss)==-1)
:");
typedef struct{
datatype data[maxsize];
datatype top; }seqstack; seqstack *s;
printf(" 算数表达式不匹配!");
else
printf(" 算数表达式匹配!"); }
void build();
void push();
void pop();
int check(char ss[]); void build(){
s=(seqstack*)malloc(sizeof(seqstack));
s->top=-1;
}
int check(char ss[]){
int i=0; while(ss[i] !=
'\0'){ i++; if(ss[i] == '(')
push(); else if(ss[i] == ')')
pop();
}
return s->top;
}
void push(){
s->top++; } void pop(){
s->top--;
} 4. 回文串判断
#include <stdio.h> #include <stdlib.h> #include <string.h> 队列为空,无法出队 printf(" exit(0); \n");
}
return stq->data[stq->front++]; #define maxsize 100 }
typedef struct { char data[maxsize]; int top; } stack;
void inqueue(queue *stq, char value) { if( isfull_queue(stq) ) { 队列已满,无法入队 printf(" exit(0);
\n");
}
stq->data[stq->rear++] = value; typedef struct { char data[maxsize]; int front; int rear; } queue;
}
stack * init_stack(){ stack *
tmp
=
(stack
*)
malloc( sizeof(stack) );
tmp->top = 0; return tmp; }
int isempty_queue(queue * stq){ return stq->front == stq->rear; }
int isfull_queue(queue *stq){ return stq->rear >= maxsize -1 ; }
int isempty_stack(stack *stk) { return stk->top == 0 ? 1 : 0; }
queue * init_queue(){ int isfull_stack(stack *stk) {
return stk->top >= maxsize -1 ? 1: 0;
}
queue
*
tmp
=
(queue*)
malloc(sizeof(queue)); tmp->front = tmp->rear = 0; return tmp; }
char pop(stack *stk) { if( isempty_stack(stk) ) { 堆栈为空,无法出栈 printf("
exit(0); \n");
char dequeue(queue * stq){ if( isempty_queue(stq) ) {
}
不是回文串 return stk->data[--stk->top]; printf(" flag = 1; break;
\n");
}
void push(stack *stk, char value) { if( isfull_stack(stk) ) { }
} 堆栈已满,无法入栈 printf(" exit(0); \n");
if( !flag ) printf("
是回文串 \n");
}
stk->data[stk->top++] = value; }
}
int main(){
queue * stq = init_queue(); stack * stk = init_stack();
void compare(stack * stk,
queue *stq,
char
str[], int len) { int i; int flag = 0; char temp_stack; char temp_queue; char c[maxsize],s; int i=0; 请输入字符序列,以 @结束 \n");
printf("
scanf("%c",&s); while(s!='@'){
c[i]=s;
scanf("%c",&s); i++;
}
c[i]='\0';
compare(stk, stq, c, strlen(c)-1); for(i = 0; i < len; i++){ push(stk, str[i]); inqueue(stq, str[i]); }
for(i = 0; i < len; i++){ temp_stack = pop(stk); temp_queue = dequeue(stq);
return 0;
if(temp_stack != temp_queue) {
}
3.3 例题
例 1: 栈和队列是特殊的线性表,其特殊性体现在?为什么要引入循环队列?
解:和普通线性表相比,对插入、删除运算加以限制。
一般的一维数组队列的尾指针已经到了数组的 上界,不能再有入队操作,但其实数组中还有空位置,为了解决这种问题,就要引入循环队列。
例 2: 设一个栈的入栈序列为 A,B,C,D, 则可能的出栈序列有哪些?
解:共计 14 种,分别是: ABC D 、 ACB D 、ACD B 、ABD C 、ADC B 、BAC D 、 BAD C 、 BCA D 、 BCD A 、 BDC A 、CBA D 、
CBD A 、 CDB A 、 DCB A 。
例 3: 有 5 个元素,其入栈次序为
A 、
B 、
C 、
D 、
E ,在各种可能的出栈次序中,以元素
C 、
D 最先出栈( C
第一个出栈且 D 第二个出栈)的次序有哪几种?
解:共计 3 种,分别是: CDEB A 、 CDBE A 、 CDB A 。
E 例 4: 判定一个顺序栈 ST (元素个数最多为
StackSize )为空 / 满的条件是。
解:空: ST->top=0 ,满: ST->top=MAXSIZE-1 。
例5:判定一个循环队列Q(存放元素位置:0 至QueueSize-1 )队满的条件是。
解:sq-> front ==(sq-> rear + 1) % maxSize
例6:若用一个大小为 6 的数组来实现环形队列,且当前和
2的值分别是。
0 和3,当从队列中删
rear front
和 4
除一个元素,再加入两个元素后,rear 和front 的值分别是
第四章串
4.1 串的基本概念
1. 串的概念
串(String) 是零个或多个字符组成的有限序列。
一般记作S=“a1a2a3 an”,其中S 是串名,用双引号括起来的字符序列是串值;ai(1 ≦i ≦n) 可以是字母、数字或其它字符;串中所包含的字符个数称为该
(Empty String) ,它不包含任何字符。
串的长度。
长度为零的串称为空串
2. 主串和子串
串中任意个连续字符组成的子序列称为该串的子串。
包含子串的串相应地称为主串。
通常将子串在主串中首次出现时的该子串的首字符对应的主串中的序号,定义为子串在主串中的序号(或位置)。
3 空白串和空串
通常将仅由一个或多个空格组成的串称为
“”
空白串(Blank String) 。
空白串和空串的不同,如“”和分别表示长度为 1 的空白串和长度为0 的空串。
4.2 串的存储结构
1. 顺序存储
#define MAXSTRLEN 256
char s[MAXSTRLEN];
2. 堆分配存储
typedef struct {
若是非空串, 则按串长分配存储区// 串长度, 否则ch 为NULL
char *ch;
int length;
} HString ;
//
这类串操作实现的算法为:先为新生成的串分配一个存储空间,然后进行串值的复制。
(1)求串长
int strlen(HString s) {
return s.length;
}
(2)置空
Status clearstring(HString s) {
if (s.ch)
{ free(s.ch); s.ch=NULL; } s.length=0;
}
( 3)生成堆
/ / 生成一个其值等于串常量
chars 的串 t
Status strassign(HString t, char *chars){
if(t.ch)
释放原空间
// 求串长
free(t.ch); i=strlen(chars); if (!i)
// 空串
{ t.ch=NULL; t.length=0; } // else{
申请存储
if(!(t.ch=(char *)malloc(i*sizeof(char))))
exit(OVERFLOW); for (j=0;j<i;j++) t.ch[j]=chars[j]; t.length=i; }
// 复制
// }
( 4)比较函数
int strcmp(HString s, HString t) { //S>T,
返回值 >0; S==T, 返回值 0 ; S<T, for(i=0;i<s.length && i<t.length; ++i)
if(s.ch[i]!=t.ch[i])
return(s.ch[i]-t.ch[i]); return s.length-t.length;
}
( 5)拼接函数
// 用 T 返回由 S1 和 S2 联接而成的新串 Status strcat(HString t, HString s1,HString s2) {
返回值 <0
if(!(t.ch)=(char*)malloc(s1.length+s2.length)*sizeof(char)))
exit(OVERFLOW); for(j=0; j< s1.length ; j++)
t.ch[j]=s1.ch[j]; for(k=0;k< s2.length ;k++)
t.ch[j+k]=s2.ch[k]; t.length=s1.length+s2.length; }
( 6)求子串
// 用 Sub 返回串 S 的第 pos 个字符起长度为 len 的子串 Status substr(HString sub, HString s, int pos, int len) {
if (pos<1 || pos>s.length || len<0 || len>s.length-pos+1)
return ERROR; if (sub.ch)
释放旧空间
free(sub.ch);
//
if (!len)
{ else{
空子串
sub.ch=NULL; sub.length=0; } // sub.ch=(char *)malloc(len*sizeof(char)); for(j=0;j<len;j++)
sub.ch[j]=s.ch[pos-1+j]; s.length=len; } }
3. 链式存储
typedef struct
node{ char data;
struct node *next; }lstring;
// 可由用户定义的块大小,实际中根据需要设置大小 // 结点结构
#define CHUNKSIZE 80 typedef struct Chunk {
char
ch[CUNKSIZE]; struct Chunk *next; 串的链表结构
// 串的头和尾指针 // 串的当前长度
typedef struct { Chunk *head, *tail; int curlen; } LString;
// 4.3 串的基本运算
1. 串赋值: ,表示将 T 串的值赋给 S 串。
strassign(S,T) 2. 联接: strcat(T1,T2) ,表示将 T1 串和 T2 串联接起来,组成一个新的
,求 T 串的长度。
T1 串。
3. 求串长度: strlen (T)
4. 子串: 该新串是
,表示截取 S 串中从第 i 个字符开始连续 len 个字符,构成一个新串(显然
substr (S, i, len) S 串的子
5. 串比较大小: strcmp(S,T) S>T ,函数值为正。
,比较 S 串和 T 串的大小,若 S<T ,函数值为负,若 S=T ,函数值为零,若
6. 串插入:
7. 串删除: ,在 S 串的第 i 个位置插入 T 串。
strinsert (S,i,T) strdelete(S,i,len)
,删除串 S 中从第 i 个字符开始连续 ,求 T 子串在 S 主串中首次出现的位置,若 len 个字符。
T 串不是 S 串的子串,则位置为 8. 求子串位置: 零。
9. 串替换: replace (S,T1,T2)
index(S,T) ,用串 T2 替换串 S 中出现的所有子串
T1。
4.4 模式匹配
1.BF 算法
(1)算法思想:
将主串的第pos 个字符和模式的第 1 个字符比较,若相等,继续逐个比较后续字符;若不等,从主串
的下一字符值为S 中与(pos+1) 起,重新与第一个字符比较。
直到主串的一个连续子串字符序列与模式相等。
返回T 匹配的子序列第一个字符的序号,即匹配成功。
否则,匹配失败,返回值0
(2)程序段:
int S_index(SString t, SString p, int pos)
{ int n,m,i,j;
m=strlen(t); n=strlen(p);
for (i=pos-1; i<=m-n; i++){
for (j=0; j<n && t[i+j]==p[j]; j++) ;
if(j==n) return(i+1);
}
return(0);
}
2. * KMP算法
(略)
例题
4.5
例1:若n 为主串长,m为子串长,则串的古典(朴素)匹配算法最坏的情况下需要比较字符的总次数
为
(n-m+1)*m 。
例2:设有两个串
解:
s 和t ,其中t 是s 的子串,求子串t 在主串s 中首次出现位置的算法。
找到返回下标(>=1) ,否则返回0; 串类型为int S_index(SString s, SString t) { //
int n,m,i,j;
m=strlen(s);
n=strlen(t);
for (i=0; i<=m-n; i++){
for (j=0; j<n && s[i+j]==t[j]; j++) ;
if(j==n) return(i+1);
}
return(0);
}
SString
第五章数组和广义表
5.1 数组的定义
在C 语言中,一个二维数组类型可以定义为其分量类型为一维数组类型的一维数组类型,也就是说:typedef elemtype array2[m][n];
等价于:
typedef elemtype array1[n]; typedef array1 array2[m];
数组一旦被定义,它的 维数 和维界 就不再改变。
因此,除了结构的 元素 和 修改元素值 的操作。
初始化 和销毁 之外,数组只有 存取
5.2 数组的存储方式
数组一般采用顺序存储,又分为
行优先 和 列优先 。
数组的地址计算具有以下前提三要素:
① ② ③
开始结点的存放地址(即基地址) 维数和每维的上、下界。
每个数组元素所占用的单元数 。
L 。
,这里 c1,c2 不一定是 0。
设一般的二维数组是
A[c1..d1, c2..d2]
行优先存储时的地址公式为:
组基地址, i-c1 为 a ij 之前的行数, 列优先存储的通式为: LOC(a ij LOC(a ij )=LOC(a c1,c2 )+[(i-c1)*(d2-c2+1)+(j-c2)]*L。
其中, 为数
c1,c2 d2-c2+1 为总列数, j-c2 为 a ij 本行前面元素个数, L 为单个元素长度。
)=LOC(a c1,c2 )+[(j-c2)*(d1-c1+1)+(i-c1)]*L。
5.3 特殊矩阵
1. 对称矩阵
( 1)定义
在一个 n 阶方阵 A 中,若元素满足下述性质:
称。
( 2)存储方式
aij=aji ( 0≤ i, j ≤ n-1 ),即元素关于主对角线对
不失一般性,按“行优先顺序”存储主对角线以下元素,存储空间节省一半,如下所示:
a 11 a 21 a 31 a a 22 a 32
33
..
n 3
a n 1
a
a
a nn
i 行(0 ≤ i<n) 恰有 i+1 个元素, 矩阵元素总数为:
n 2
在这个下三角矩阵中, ,
1+2+
中。
i-1 +n=n*(n+1)/2 因此,可以按从上到下、从左到右将这些元素存放在一个向量
sa[0..n(n+1)/2-1]
若 +i=i*(i+1)/2 k=i*(i+1)/2+j
若 可
i ≥ j ,则
aij
在下三角矩阵中。
aij
之前的 i 行 ( 从第 个元素 ( 即 0 行到第 ai0,ai1,ai2, 行 ) 一共有 1+2+
个元素,在第
,0≤ k<n(n+1)/2 i 行上 。
之前恰有 ,因此:
aij
j ,aij-1) i<j ,则 aij 是在上三角矩阵中。
因为 ,所以只要交换上述对应关系式中的
i 和 j 即
aij=aji
得到: , 0≤ k<n(n+1)/2。
k=j*(j+1)/2+i
令 I=max(i,j), J=min(i,j),
k=I*(I+1)/2+J
则 k 和 i,j
的对应关系统一为:
, Loc(aij) = Loc(sa[k]) = Loc(sa[0])+k*d
2. 三角矩阵
( 1)定义
以主对角线划分,三角矩阵有上三角和下三角。
上三角矩阵 :它的下三角 ( 不包括主对角线 ) 中的
元素均为 常数 。
下三角矩阵 正好相反, 它的主对角线上方均为 常数 。
在大多数情况下, 三角矩阵 常数为零 。
( 2)存储方式
三角矩阵中的 重复元素 c 可共享一个存储空间,其余的元素正好有
个,因此,三角矩
n(n+1)/2 阵可压缩存储到向量
中,其中 c 存放在向量的最后一个分量中。
sa[0..n(n+1)/2]
上三角矩阵:只存放上三角部分。
,当 i ≤ j
a00 = sa[0], a01 = sa[1], a02 = sa[2],
时, aij 在上三角部分中,前面共有 i 行,共有 n+n-1+n-2+ +n-(i-1) 元 素 。
= i*n-i*(i-1)/2 = i*(2n-i+1)/2 对 应 关 系 为 :
个 元 素 , 在 第
i
行 上 , aij
前 恰 好 有
个 和 j-i
sa[k] aij
下三角矩阵的存储和对称矩阵用下三角存储类似, ,
a00 =sa[0], a10 =sa[1], a11 = sa[2], sa[k] 和 aij 对应关系为:
3. 对角矩阵(三对角矩阵为例)
( 1)定义
对角矩阵中,所有的 非零元素 集中在 以主对角线为中心的带状区域中
,即除了主对角线和主对角
线相邻两侧的若干条对角线上的元素之外,
( 2)存储方式
非零元素仅出现在 主对角线 (aii, 0
其余元素皆为零 。
≤ i ≤ n-1 )
上,紧邻主对角线 上面 的那条对角线上 (aii+1, 0
≤ i ≤ n-2) 和紧邻主对角线
下面 的那条对角线上 i, 0≤i ≤ n-2) 。
显然,当 | i-j
|>1 时,元素 aij=0 。
(ai+1 在一个 n*n 的三对角矩阵中,只有 储单元。
个非零元素,故只需 3n-2 个存储单元,零元已不占用存
(n-1)+n+(n-1)
将 n*n 的三对角矩阵 A 压缩存放到只有 3n-2 个存储单元的 sa 向量中, 假设仍 按行优先 顺序存放
则 sa[k] 与 aij 的对应关系为为:
在 之前有 行,共有 个非零元素, 在第 行,有 个非零元素, 即非零元素 。
aij i 3*i-1 i j-i+1 aij
的地址为: Loc(aij) = Loc(sa[k]) =LOC(0,0)+[3*i-1+(j-i+1)]*d=LOC(0,0)+(2*i+j)*d
5.4 稀疏矩阵及存储
1. 概念
在实际应用中,经常会遇到另一类矩阵:其矩阵 阶数很大 ,非零元 个数 较少 ,零元很多 ,且非零元的
排列 无规律 可寻,则称这类矩阵为
稀疏矩阵 。
精确地说,设在的矩阵
A 中,有 s 个非零元。
令 ,称 e 为矩阵 A 的 稀疏因子 。
通常认
e = s / (m*n)
为 e ≤ 0.05 时称矩阵 A 为稀疏矩阵。
稀疏矩阵由表示 个 非零元 。
非零元 的 三元组 及 行列数唯一确定 , 一个三元组 (i, j, aij) 唯一确定 了矩阵 A 的一 例如:下列 三元组表 : ( (0,1,12), (0,2,9),
(2,0,-3),
(2,5,14),
(3,2,24),
(4,1,18),
(5,0,15),
,加上 —— 矩阵的行数、列数及非零元数
便可作为矩阵 M 的另一种描
述:
(5,3,-7) )
(6,7,8)
2. 三元组顺序表
假设以 顺序存储结构 来表示三元组表,则可得到稀疏矩阵的一种压缩存储方法—— 义如下:
#define maxsize 1000 typedef int datatype; typedef struct {
三元顺序表 。
其定
非零元的行、列下标 元素值 */
int i,j; /* */
datatype v; /* } triplet; typedef struct {
/* 三元组表 行数、列数、非零元素个数 triplet data[maxsize];
*/ int m,n,t; } tripletable; /* */ /* 稀疏矩阵类型 */
因此上面的三元组表的三元组顺序表表示如下:
M.data i j v M[0].i=6
0 0 1 2 12 9
2 0 14 24 18 15 -7
M[0].j=7 M[0].t=8
-3 2 3 4 5 5 5 2 1 0 3 显然,三元组顺序表存储会失去随机存取功能。
3. 三元组顺序表的转置
一个 m × n 的矩阵 A ,它的转置 B 是一个 n × m 的矩阵,且 , 0≤ i<m ,0≤ j<n ,即 A
a[i][j]=b[j][i] 的行是 将 B 的列, A 的列是 B 的行。
A 转置为
B ,就是将 A 的三元组
a.data 置换为表 B 的三元组表
b.data ,如果只是简单地交换 a.data
中 的 i 和 j 的内容, 那么得到的 b.data 将是一个 按列优先 顺序存储的稀疏矩阵 B ,要得到 按行优先 顺序存储 b.data ,就必须 重新排列三元组的顺序。
解决思路:只要做到:①将矩阵行、列维数互换;②将每个三元组中的
组次序, 使 mb 中元素以 N 的行 (M 的列 ) 为主序 。
( 1)方法一: 按 M 的列序转置
i 和 j 相互调换;③ 重排 三元
即按 mb 中三元组次序依次在 ma 中找到相应的三元组进行转置。
为找到 M 中每一列所有非零元素,需 对其三元组表 ma 从第一行起扫描一遍。
由于 ma 中以 M 行序为主序 , 所以由此得到的恰是
mb 中应有的顺
序。